【问题标题】:getViewLifecycleOwner() in DialogFragment leads to crashDialogFragment 中的 getViewLifecycleOwner() 导致崩溃
【发布时间】:2019-02-19 12:15:11
【问题描述】:

我使用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

标签: android viewmodel dialogfragment android-livedata


【解决方案1】:

这是因为 DialogFragment 是如何创建的。 如果您使用onCreateDialog(),那么这种类型的 Fragment 会使用稍微不同的生命周期。 onCreateView() 不会被使用,因此这个 Fragment 的 viewLifecycleOwner 不会被初始化。

作为一种解决方法,您可以使用 Fragment 实例作为观察者的所有者: .observe(this, Observer {...}。虽然使用this 而不是viewLifecycleOwner 会收到警告。

【讨论】:

  • 我似乎没有收到使用this (MyDialogFragment) 的警告。这种方法比使用getActivity()requireActivity() 更可取吗?
  • @ban-geoengineering 只要您不使用同一片段实例多次show DialogFragment。如果这样做,观察者将被第二次订阅,并且由于双重观察,您可能会遇到奇怪的问题。这是因为通过使用this 作为生命周期所有者,观察者只有在调用onDestroy 时才会被移除,而不会通过简单地关闭对话框来调用。
【解决方案2】:

This is the official recommendationDialogFragments:

注意:在订阅诸如LiveData 之类的生命周期感知组件时,您决不能在使用DialogsDialogFragment 中将viewLifecycleOwner 用作LifecycleOwner。相反,请使用 DialogFragment 本身,或者如果您使用的是 Jetpack Navigation,请使用 NavBackStackEntry

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

【讨论】:

    【解决方案3】:

    您的情况略有不同,但我认为这个概念是相同的。只需在 Dialog 类中使用 this.getActivity() 并将其传递为 LifeCycleOwner。我遇到了同样的问题,因为我使用了 LiveDataRetrofitLiveData 需要参考。 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;
    }
    

    【讨论】:

    • 如果片段被销毁然后重新创建,但活动没有怎么办?旧的观察者仍然引用旧的片段,你给自己带来了内存泄漏! viewLifecycleOwner 的重点是在片段被销毁时移除观察者。
    【解决方案4】:

    这是因为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>
    

    【讨论】:

      【解决方案5】:

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

      我的组件正在使用 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 架构上的一些管理不善,我不会更改我的组件...

      【讨论】:

        【解决方案6】:

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

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

        【讨论】:

          猜你喜欢
          • 2013-11-03
          • 1970-01-01
          • 2018-01-23
          • 2020-12-02
          • 2013-11-11
          • 2011-01-24
          • 2015-02-23
          • 2011-04-23
          • 1970-01-01
          相关资源
          最近更新 更多