DialogFragment中的getViewLifecycleOwner()导致崩溃

36

我使用DialogFragment (onCreateDialog)和ViewModel来实现。但是,当我尝试将getViewLifecycleOwner()传递给LiveData::observe方法时,会出现以下错误:

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

DialogFragment 中使用 getViewLifecycleOwner() 是可能的吗?


onCreateDialog 中,对话框尚未创建。请尝试在 onViewCreated() 中创建。我自己并没有经常使用 LifecycleOwner - ADM
6个回答

17
这是DialogFragment官方推荐的做法:(参考文档) 请注意:当订阅类似LiveData的组件时,您不应将viewLifecycleOwner作为使用DialogsDialogFragment中的LifecycleOwner。相反,使用DialogFragment本身,如果您正在使用Jetpack Navigation,则使用NavBackStackEntry
因此,您可以像平常一样观察事物,但是要将this或当前后退栈条目(例如findNavController().currentBackStackEntry)传递而不是viewLifecycleOwner。无需覆盖onCreateView来强制创建viewLifecycleOwner或其他任何内容!

16
这是因为DialogFragment的创建方式不同导致的。 如果使用onCreateDialog(),则此类型的Fragment将使用稍微不同的生命周期。将不会使用onCreateView(),因此该Fragment的viewLifecycleOwner不会被初始化。
作为解决方法,您可以使用Fragment实例作为观察者的所有者: .observe(this, Observer {...}。尽管您将因使用this而不是viewLifecycleOwner而收到警告。

1
我似乎在使用this(MyDialogFragment)时没有收到任何警告。这种方法比使用getActivity()requireActivity()更可取吗? - ban-geoengineering
5
只要不使用相同的碎片实例多次“显示”DialogFragment,就不会出现问题。如果这样做,观察者将第二次被订阅,因此可能会出现由于重复观察而引起的奇怪问题。这是因为在使用“this”作为生命周期所有者时,观察者仅在调用“onDestroy”时才会被删除,并且仅仅关闭对话框是不会调用“onDestroy”的。 - Nicolas

8
你的情况略有不同,但我认为概念基本相同。在你的对话框类中使用this.getActivity()并将其作为LifeCycleOwner传递。我遇到了同样的问题,因为我使用了LiveDataRetrofit,而LiveData需要一个引用。 DialogFragment在某个时候设置了它的LifeCycleOwner,但它并不在上面提到的任何方法中。通过使用getActivity(),你可以在onCreateDialog方法中尽早使用观察者。以下是我的一部分代码,当我尝试传递空引用this.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;
}

7
如果片段被销毁后重新创建,但活动没有被销毁呢?旧的观察者仍然引用旧的片段,这样就会导致内存泄漏!viewLifecycleOwner 的整个作用就是在片段被销毁时移除观察者。 - Nicolas

7
这是因为 DialogFragment 的生命周期与 Fragment 不同;onCreateDialogonCreateView 之前调用,因此无法使用 viewLifecycleOwner... 我通过以下方式解决了这个问题:
  • 实现 onCreateView 而不是 onCreateDialog
    • 可以从 onCreateView 中访问 viewLifecycleOwner
    • 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>

0

我的解决方案有点古怪...

我的组件正在使用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架构的某些错误管理而改变我的组件...


0

DialogFragment弹出在父片段的视图之上,因此可以使用其生命周期所有者。 因此,代码将如下所示:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接