【问题标题】:Multiple LiveData Observers After Popping Fragment弹出片段后的多个 LiveData 观察者
【发布时间】:2018-08-17 09:35:37
【问题描述】:

问题

总结:在导航到新 Fragment、弹出新 Fragment 并返回原始片段。

详细信息:该架构由 MainActivity 组成,其中承载一个 HomeFragment 作为 MainActivity 的 开始目的地 strong>导航图。 HomeFragment 内部是一个以编程方式膨胀的 PriceGraphFragmentHomeFragment 正在使用导航组件来启动一个新的子 Fragment ProfileFragment。按下后,ProfileFragment 被弹出,应用程序返回到承载 PriceGraphFragment 的 HomeFragment。 PriceGraphFragment 是 Observer 被多次调用的地方。

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

实施

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

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

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

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

  3. 在原始 Fragment (PriceGraphFragment) 中侦听来自 ViewModel 的数据。这会被多次调用,但是在加载 Fragment 时它只需要一个 Observer。

    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. 将创建 Observer 的方法移至 Fragment 的 onCreate()onActivityCreated() 方法。
  4. 在方法observe(@NonNull LifecycleOwner owner, @NonNull Observer&lt;? super T&gt; observer) 中对LifecycleOwner 使用viewLifecycleOwner 而不是this
  5. 存储在 ViewModel 中显示为重复的 HashMap 数据,而不是 Fragment。
  6. 使用ChildFragmentManagerSupportFragmentManager(在活动级别)启动子片段。

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

后续步骤

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

父片段

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 对象。

【问题讨论】:

  • 关于 1:为什么要将 View 引用存储在 ViewModel 中? ViewModel 不能有任何对 View 的引用。如果您使用数据绑定,您可以在创建视图时通过代码设置点击侦听器,或者在 ViewModel 中放置一个返回点击侦听器的方法并绑定到布局中的按钮。
  • 关于 3:你在哪里注册你的观察者?确保它在onActivityCreated() 中完成,并使用viewLifeCycleOwner 作为生命周期而不是this。如果在那之后观察者仍然被多次调用,那可能只是因为你的LiveData被多次更新了。
  • 只有在没有savedInstanceState 并且您在FragmentTransaction 上使用add 而不是replace 时,您才调用PriceGraphFragment.newInstance() - 这有什么原因吗?您是否还检查过是否每次返回ParentFragment 时都不会调用它,因为如果是这样,您基本上只是每次都在旧的PriceGraphFragment 之上添加一个新的PriceGraphFragment,这就是为什么您要多次调用观察者的原因,因为所有 PriceGraphFragment-instances 都在观察相同的 LiveData,因此发送了多个信号。
  • 要添加子片段,您需要使用 childFragmentManager,而不是 fragmentManager
  • 如果可能的话,您能否在 Github 上提供一个小型工作项目来解决这个问题?当始终存在多个小问题时,很难弄清楚问题。我的猜测是你有一些东西坚持ViewModel,然后间接地坚持Fragment,当片段的新实例出现并在你的ViewModel上调用observe时,它也会调用回到原来的ViewModel

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


【解决方案1】:

这基本上是架构中的一个错误。你可以阅读更多关于它的信息here。您可以使用getViewLifecycleOwner 代替observer 中的这个来解决它。

像这样:

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

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

【讨论】:

  • 感谢@KvidLingen,从我最初的帖子中您可以看到我已经尝试过 getViewLifecycleOwner 解决方案。我已经更新了我的初始帖子,还表明我已经尝试了onActivityCreated() 解决方案,但没有成功。
  • 我什至在我的子片段中使用它,弹出后所有观察都从子片段开始。
【解决方案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 查询),但仅在 saveInstanceStatenull 时才这样做>.

非因素

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

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

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

【讨论】:

  • 用“replace”替换“add”为我做了。谢谢
  • 但是……你根本没有使用导航组件吗?
【解决方案3】:

最好初始化视图模型,观察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,它仍然会为您提供相同的视图模型对象,因为它在 Fragment 的生命周期中只创建一次。

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

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

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

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

        viewModel.fetchData()

        ...
    }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多