getViewLifecycleOwner() в DialogFragment приводит к сбою

Я использую DialogFragment (onCreateDialog) и ViewModel для него. Но, когда я пытаюсь использовать getViewLifecycleOwner() для передачи в методе наблюдаю (), я получаю ошибку, как показано ниже:

java.lang.IllegalStateException: Can't access the Fragment View's LifecycleOwner when getView() is null i.e., before onCreateView() or after onDestroyView().

Возможно ли использовать getViewLifecycleOwner() внутри DialogFragment?

8 ответов

Это официальная рекомендация для s:

Примечание. При подписке на компоненты с учетом жизненного цикла, такие как LiveData, вы никогда не должны использовать в качестве LifecycleOwner в том, что использует Dialogs. Вместо этого используйте DialogFragment сам, или если вы используете Jetpack Navigation, используйте NavBackStackEntry.

Таким образом, вы можете просто наблюдать за вещами как обычно, но вместо того, чтобы проходить this, или текущая запись backstack (например, findNavController().currentBackStackEntry). Нет необходимости отменять onCreateView просто чтобы заставить viewLifecycleOwner быть созданным или что-нибудь!

Это происходит из-за того, как создается DialogFragment. Если вы используетеonCreateDialog()чем для этого типа фрагментов используется несколько иной жизненный цикл. ВonCreateView() не будет использоваться, поэтому viewLifecycleOwner для этого фрагмента не будет инициализирован.

В качестве обходного пути вы можете использовать экземпляр Fragment в качестве владельца для наблюдателя:.observe(this, Observer {...}. Хотя вы получите предупреждение за использованиеthis вместо viewLifecycleOwner.

Ваш случай немного отличается, но я думаю, что концепция такая же. Просто используйтеthis.getActivity() в вашем Dialog Class и передайте его как LifeCycleOwner. У меня была такая же проблема, потому что я использовалLiveData а также Retrofit а также LiveDataнужна ссылка. ВDialogFragment устанавливает свои LifeCycleOwner в какой-то момент, но этого нет ни в одном из упомянутых выше методов. ИспользуяgetActivity() вы можете использовать своего наблюдателя уже в методе onCreateDialog. Вот некоторая часть моего кода, которая сначала вызвала некоторую проблему, когда я пытался передать ссылку на nullthis.getViewLifecycleOwner() вместо деятельности.

@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
       FragmentActivity activity = this.getActivity();
       binding = DialogSelectIssuesBinding.inflate(LayoutInflater.from(getContext()));

       RetroRepository.
            getDefault().
            getAllIssues().
            observe(this.getActivity(), listLiveDataResponse -> {
                //ToDo Check for errors and Bind the data here 
            });


       AlertDialog alertDialog = new AlertDialog.Builder(activity)
                            .setView(binding.getRoot())
                            .setTitle("Please select issues from the list below:")
                            .setNegativeButton("CANCEL", null)
                            .setPositiveButton("ADD", null)
                            .create();
       alertDialog.setCanceledOnTouchOutside(false);
       return alertDialog;
}

Это происходит потому, что жизненный цикл DialogFragment отличается от Fragment; onCreateDialog называется раньше onCreateView, так viewLifecycleOwner недоступен... Я решил проблему:

  • реализация onCreateView вместо того onCreateDialog
    • может получить доступ viewLifecycleOwner изнутри onCreateView
    • вид вернулся из onCreateView помещен в диалог для нас DialogFragment...
    • вам нужно будет создать свои собственные кнопки и заголовки в диалоговом окне...

дополнительный код:

class TextInputDialogFragment : DialogFragment() {

    ...

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
    ): View? {
        val viewBinding = FragmentDialogTextInputBinding.inflate(layoutInflater, container, false)

        val titleText = params.title.localize(requireContext())
        viewBinding.toolbar.isVisible = titleText.isNotBlank()
        if (titleText.isNotBlank()) {
            viewBinding.toolbar.title = titleText
        }

        viewBinding.recyclerview.adapter = ListItemAdapter(
            viewLifecycleOwner, requireContext().app.nowFactory, viewModel.fields
        )

        viewBinding.buttonAffirm.setOnClickListener {
            listener.onOkPressed(viewModel.userInputtedText.value)
            dismiss()
        }

        viewBinding.buttonReject.setOnClickListener {
            dismiss()
        }

        viewModel.enablePositiveButton.observe(viewLifecycleOwner) { isEnabled ->
            viewBinding.buttonAffirm.isEnabled = isEnabled
        }

        return viewBinding.root
    }

    ...
}

используемый файл макета

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:minHeight="?attr/actionBarSize"
            tools:title="Title" />

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerview"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />

        <LinearLayout
            style="?buttonBarStyle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:clickable="false"
            android:gravity="end"
            android:orientation="horizontal"
            android:padding="@dimen/min_touch_target_spacing_half">

            <Button
                android:id="@+id/button_reject"
                style="?buttonBarButtonStyle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="@dimen/min_touch_target_spacing_half"
                android:text="@android:string/cancel" />

            <Button
                android:id="@+id/button_affirm"
                style="?buttonBarButtonStyle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="@dimen/min_touch_target_spacing_half"
                android:text="@android:string/ok" />

        </LinearLayout>

    </LinearLayout>

</layout>

Поскольку DialogFragment появляется поверх родительского фрагмента, он может использовать его владельца жизненного цикла. Таким образом, код будет выглядеть так:

      parentFragment?.viewLifecycleOwner?.let {
                binding.lifecycleOwner = it
            }

Существует вероятность того, что вы в первый раз возвращаете пустой экземпляр LiveData из репозитория /ViewModel. убедитесь, что вы инициировали LiveData в репозитории /ViewModel.

Возможно ли использовать getViewLifecycleOwner() внутри DialogFragment?

да

Попробуй поставить свою логику внутри onActivityCreated(), поскольку в этот раз ваше представление не будет нулевым, что вызвало это исключение.

Также вы можете использовать getViewLifecycleOwnerLiveData() и посредством прикрепления к нему наблюдаемой, вы можете поместить свою логику в эту наблюдаемую, которая начнется, как только lifeCycleOwner будет инициализирован.

Мое решение было немного странным...

Мой компонент использовал getViewLifecycleOwnerLiveData() .... так:

private final MyLifeCycleOwner owner = new MyLifeCycleOwner();

private final MutableLiveData<LifecycleOwner> result = new MutableLiveData<>();

@NonNull
@Override
public LiveData<LifecycleOwner> getViewLifecycleOwnerLiveData() {
    return result;
}

@Override
public void onDestroyView() {
    super.onDestroyView();
    owner.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
    result.setValue(null);
}

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    result.setValue(owner);
    owner.getLifecycle();
    owner.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
    return super.onCreateView(inflater, container, savedInstanceState);
}

Поскольку FragmentViewLifecycleOwner является частным пакетом... это причина класса MyLifeCycleOwner.

Я не собирался менять свои компоненты из-за неправильного управления архитектурой Android...

Другие вопросы по тегам