DialogFragment中的getViewLifecycleOwner()导致崩溃

时间:2019-02-19 12:15:12

标签: android viewmodel dialogfragment android-livedata

我使用DialogFragment (onCreateDialog)和ViewModel。但是,当我尝试使用getViewLifecycleOwner()传递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()?

8 个答案:

答案 0 :(得分:7)

发生这种情况的原因是如何创建DialogFragment。 如果您使用onCreateDialog(),则此类型的Fragment使用略有不同的生命周期。 onCreateView()将不会被使用,因此此Fragment的viewLifecycleOwner将不会初始化。

为此,您可以使用Fragment实例作为观察者的所有者: .observe(this, Observer {...}。尽管您会收到使用this而不是viewLifecycleOwner的警告。

答案 1 :(得分:2)

发生这种情况是因为DialogFragment的生命周期不同于FragmentonCreateDialogonCreateView之前被调用,因此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>

答案 2 :(得分:1)

This is the official recommendation 用于 DialogFragment

<块引用>

注意:在订阅生命周期感知组件(例如 LiveData)时,切勿将 viewLifecycleOwner 用作 LifecycleOwner 中的 DialogFragment使用 Dialogs。相反,请使用 DialogFragment 本身,或者如果您使用的是 Jetpack Navigation,请使用 NavBackStackEntry

所以你可以像往常一样观察事情,但你传递的不是 viewLifecycleOwner,而是 this,或者当前的 backstack 条目(例如 findNavController().currentBackStackEntry)。无需覆盖 onCreateView 只是为了强制创建 viewLifecycleOwner 或任何东西!

答案 3 :(得分:0)

  

是否可以在DialogFragment中使用getViewLifecycleOwner()?

请尝试将您的逻辑放入matdialog.open(OrderComponent, { data: {editId: "youruniqueid" } });中,因为那时候您的视图将不会为null,这会导致该异常。

您还可以使用onActivityCreated()并通过在其上附加可观察对象,可以将逻辑放入该可观察对象中,该逻辑将在初始化getViewLifecycleOwnerLiveData()后立即开始。

答案 4 :(得分:0)

此错误有可能是您第一次从Repository / ViewModel返回空的LiveData实例。确保您是否已在Repository / ViewModel中启动LiveData。

答案 5 :(得分:0)

您的情况略有不同,但我认为概念是相同的。只需在对话框类中使用 this.getActivity() 并将其作为 LifeCycleOwner 传递即可。我遇到了同样的问题,因为我使用了 LiveData Retrofit 并且LiveData需要参考。 DialogFragment 在某个时候设置其 LifeCycleOwner ,但是没有采用上述任何方法。通过使用 getActivity() ,您可以最早在onCreateDialog方法中使用观察者。这是我的代码的一部分,当我尝试传递引用为null的 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;
}

答案 6 :(得分: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架构管理不善,我不会更改组件。

答案 7 :(得分:0)

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

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