弹出片段后的多个LiveData观察者

时间:2018-08-17 09:35:38

标签: android android-fragments kotlin android-navigation android-livedata

问题

摘要:导航到新的片段,弹出新的片段,然后返回到,然后在一个片段中触发多个 LiveData 观察者原始片段。

详细信息:该体系结构由 MainActivity 组成,该主机在MainActivity的< strong>导航图。 HomeFragment中有一个以编程方式夸大的 PriceGraphFragment HomeFragment 正在使用导航组件启动新的子片段 ProfileFragment 。背面按一下,将弹出ProfileFragment,应用程序返回到承载PriceGraphFragment的HomeFragment。 PriceGraphFragment是多次调用Observer的地方。

我正在记录观察者正在发出的HashMap的哈希码,当我转到概要文件Fragment,弹出概要文件Fragment并返回价格Fragment时,它显示2个唯一的哈希码。这与我在不启动配置文件Fragment的情况下旋转屏幕时从HashMap中看到的一个哈希码相反。

实施

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

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

  2. 在片段(PriceGraphFragment)中创建
  3. ViewModel 。 ViewModel已被记录,并且具有多个Observer的数据仅在ViewModel中初始化了一次数据。

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

  4. 从原始片段(PriceGraphFragment)中的 ViewModel 监听数据。这被多次调用,但是预计只有在加载Fragment时才有一个Observer。

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

尝试的解决方案

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

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

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

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

后续步骤

  • 也许该问题与在 ParentFragment 的( HomeFragment 中创建嵌套的 ChildFragment PriceGraphFragment ) >)onViewCreated()

ParentFragment

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可观察对象替换LiveData对象。

3 个答案:

答案 0 :(得分:3)

这基本上是体系结构中的错误。您可以详细了解here。您可以使用getViewLifecycleOwner代替observer来解决此问题。

赞:

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

并将此代码放在onActivityCreated()中,因为使用getViewLifecycleOwner需要一个视图。

答案 1 :(得分:2)

首先,谢谢所有在此发布的人。您的建议和建议相结合,在过去5天内帮助我解决了该错误,因为其中涉及多个问题。

问题已解决

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

之前:

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()中而不是onViewCreated()中初始化子片段(PriceFragment)的数据请求(Firebase Firestore查询)数据,但只有在 saveInstanceState

非因素

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

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

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

答案 2 :(得分:0)

最好初始化视图模型并观察 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
        }
    }

然而,无论您在哪里初始化视图模型,无论是在 onCreateonViewCreated 中,它仍然会为您提供相同的视图模型对象,因为它在 Fragment 的生命周期中只创建一次。

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

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

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

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

        viewModel.fetchData()

        ...
    }