弹出片段后多个 LiveData 观察者

Multiple LiveData Observers After Popping Fragment

问题

Summary: 多个 LiveData Observers 在导航到一个片段后被触发new Fragment,弹出新的Fragment,然后returning到原来的Fragment。

Details:该架构由 MainActivity 组成,它托管一个 HomeFragment 作为 MainActivity 的 导航图 中的 起始目的地 。在 HomeFragment 中是一个以编程方式膨胀的 PriceGraphFragmentHomeFragment 正在使用导航组件启动新的子片段 ProfileFragment。在返回时,ProfileFragment 弹出,应用程序 returns 到托管 PriceGraphFragment 的 HomeFragment。 PriceGraphFragment 是 Observer 被多次调用的地方。

我正在记录 Observer 发出的 HashMap 的哈希码,当我转到配置文件片段、弹出配置文件片段和 return 到价格片段时,它显示了 2 个唯一的哈希码。这与我在不启动配置文件片段的情况下旋转屏幕时从 HashMap 看到的一个哈希码相反。

实施

  1. 用于在 HomeFragment 中启动新 ProfileFragment 的导航组件。

    view.setOnClickListener(Navigation.createNavigateOnClickListener( R.id.action_homeFragment_to_profileFragment, null))

  2. ViewModel 在 Fragment (PriceGraphFragment) 中创建。 ViewModel 已经被记录,并且有多个 Observers 的数据只在 ViewModel 中初始化了一次数据。

    override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) priceViewModel = ViewModelProviders.of(this).get(PriceDataViewModel::class.java) }

  3. 在原始 Fragment (PriceGraphFragment) 中监听来自 ViewModel 的数据。这被调用了多次,但是当 Fragment 被加载时它应该只有一个观察者。

    priceViewModel.graphLiveData.observe( this, Observer { priceGraphDataMap: HashMap<Exchange, PriceGraphLiveData>? -> // This is being called multiple times. })

尝试过的解决方案

  1. onCreate() 方法中创建 FragmentViewModelpriceViewModel = ViewModelProviders.of(this).get(PriceDataViewModel::class.java)
  2. 使用 Fragment 的 activity 和子 Fragment 的父 Fragment 创建 ViewModel。
    priceViewModel = ViewModelProviders.of(activity!!).get(PriceDataViewModel::class.java)

    priceViewModel = ViewModelProviders.of(parentFragment!!).get(PriceDataViewModel::class.java)

  3. 将创建观察者的方法移动到片段的 onCreate()onActivityCreated() 方法。
  4. 在方法 observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer).
  5. 中对 LifecycleOwner 使用 viewLifecycleOwner 而不是 this
  6. 存储在 ViewModel 中显示为重复项的 HashMap 数据,而不是片段。
  7. 使用 ChildFragmentManagerSupportFragmentManager(在 Activity 级别)启动子片段。

类似问题和建议的解决方案

后续步骤

父片段

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    user = viewModel.getCurrentUser()
     if (savedInstanceState == null) {
         fragmentManager
                ?.beginTransaction()
                ?.replace(binding.priceDataContainer.id, 
                   PriceGraphFragment.newInstance())
                ?.commit()
}
  • 测试用 RxJava observables 替换 LiveData 对象。

这基本上是架构中的错误。您可以阅读更多相关信息 here。您可以在 observer.

中使用 getViewLifecycleOwner 而不是这个来解决它

像这样:

mViewModel.methodToObserve().observe(getViewLifecycleOwner(), new Observer<Type>() {
        @Override
        public void onChanged(@Nullable Type variable) {

并将此代码放在 onActivityCreated() 中,因为 getViewLifecycleOwner 的使用需要查看。

首先,感谢所有在这里发帖的人。在过去的 5 天里,由于涉及多个问题,正是您的建议和指导帮助我解决了这个错误。

问题已解决

  1. 在父 Fragment (HomeFragment) 中正确创建嵌套 Fragment。

之前:

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                          savedInstanceState: Bundle?): View? {

        if (savedInstanceState == null) {
        fragmentManager
                ?.beginTransaction()
                ?.add(binding.priceDataContainer.id, PriceGraphFragment.newInstance())
                ?.commit()
        fragmentManager
                ?.beginTransaction()
                ?.add(binding.contentFeedContainer.id, ContentFeedFragment.newInstance())
                ?.commit()
    }
...
}

之后:

override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)

    if (savedInstanceState == null
            && childFragmentManager.findFragmentByTag(PRICEGRAPH_FRAGMENT_TAG) == null
            && childFragmentManager.findFragmentByTag(CONTENTFEED_FRAGMENT_TAG) == null) {
        childFragmentManager.beginTransaction()
                .replace(priceDataContainer.id, PriceGraphFragment.newInstance(),
                        PRICEGRAPH_FRAGMENT_TAG)
                .commit()
        childFragmentManager.beginTransaction()
                .replace(contentFeedContainer.id, ContentFeedFragment.newInstance(),
                        CONTENTFEED_FRAGMENT_TAG)
                .commit()
    }
...
}
  1. onCreate() 中为父片段和子片段创建 ViewModel 而不是 onCreateView()

  2. 初始化 onCreate() 中子片段 (PriceFragment) 的数据(Firebase Firestore 查询)数据请求,而不是 onViewCreated(),但仅在 时才这样做saveInstanceStatenull.

非因素

提出了一些建议,但结果证明对解决此错误没有影响。

  1. onActivityCreated() 中创建 观察者 。我将我的保存在子片段 (PriceFragment) 的 onViewCreated() 中。

  2. Observer 创建中使用 viewLifecycleOwner。我之前使用的是子片段 (PriceFragment) 的 this。尽管 viewLifecycleOwner 不会影响这个错误,但它似乎是总体上的最佳实践,所以我保留了这个新实现。

最好在 onCreate 中初始化视图模型并观察实时数据对象。

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel = ViewModelProvider(this).get(MyFragmentViewModel::class.java)

        // 'viewLifecycleOwner' is not available here, so use 'this'
        viewModel.myLiveData.observe(this) {
            // Do something
        }
    }

然而,无论您在哪里初始化视图模型,无论是在 onCreate 还是 onViewCreated,它仍然会为您提供相同的视图模型对象,因为它在片段.

重要的部分是观察onCreate中的实时数据。因为 onCreate 仅在创建片段时调用,所以您只调用了一次 observe

onViewCreated 在创建片段时和从返回堆栈中返回时(在其上弹出片段之后)都会被调用。如果您观察 onViewCreated 中的实时数据,它将在从后台堆栈返回时立即获取您的实时数据从上一次调用中保存的现有数据。

相反,仅使用 onViewCreated 从视图模型中获取数据。因此,每当片段出现时,无论是在创建时还是从返回堆栈返回时,它总是会获取最新数据。

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        viewModel.fetchData()

        ...
    }