【问题标题】:Flow onEach/collect gets called multiple times when back from Fragment从 Fragment 返回时,Flow onEach/collect 被多次调用
【发布时间】:2021-07-29 00:12:16
【问题描述】:

我使用 Flow 而不是 LiveData 来收集 Fragment 中的数据。在片段 A 中,我观察(或者更确切地说是收集)片段的 onViewCreated 中的数据,如下所示:

lifecycleScope.launchWhenStarted {
            availableLanguagesFlow.collect {
                languagesAdapter.setItems(it.allItems, it.selectedItem)
            }
        }

问题。然后,当我转到片段 B 然后返回片段 A 时,我的 collect 函数被调用了两次。如果我再次去 Fragment B 并返回 A - 那么 collect 函数被调用 3 次。以此类推。

【问题讨论】:

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


    【解决方案1】:

    原因

    这是因为tricky Fragment lifecycle。当您从片段 B 回到片段 A 时,片段 A 会重新连接。结果片段的 onViewCreated 被第二次调用,您观察到同一 Flow 实例第二次。换句话说,现在你有一个 Flow 有两个观察者,当这个 flow 发出数据时,就会调用其中的两个。

    片段解决方案 1

    在 Fragment 的 onViewCreated 中使用 viewLifecycleOwner。更具体地说,使用 viewLifecycleOwner.lifecycleScope.launch 而不是生命周期范围.launch。像这样:

    viewLifecycleOwner.lifecycleScope.launchWhenStarted {
                availableLanguagesFlow.collect {
                    languagesAdapter.setItems(it.allItems, it.selectedItem)
                }
            }
    

    活动解决方案 2

    在 Activity 中,您可以简单地在 onCreate 中收集数据。

    lifecycleScope.launchWhenStarted {
                availableLanguagesFlow.collect {
                    languagesAdapter.setItems(it.allItems, it.selectedItem)
                }
            }
    

    其他信息

    1. LiveData 也是如此。见帖子here。另请查看this article。
    2. 使用 Kotlin 扩展使代码更简洁:

    扩展名:

    fun <T> Flow<T>.launchWhenStarted(lifecycleOwner: LifecycleOwner) {
        lifecycleOwner.lifecycleScope.launchWhenStarted {
            this@launchWhenStarted.collect()
        }
    }
    

    在片段 onViewCreated 中:

    availableLanguagesFlow
        .onEach {
            //update view
        }.launchWhenStarted(viewLifecycleOwner)
    

    更新

    我宁愿现在使用repeatOnLifecycle,因为当生命周期低于状态时它取消正在进行的协程(在我的例子中是onStop)。在没有repeatOnLifecycle 的情况下,集合将在 onStop 时暂停。查看this article。

    fun <T> Flow<T>.launchWhenStarted(lifecycleOwner: LifecycleOwner)= with(lifecycleOwner) {
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED){
                try {
                    this@launchWhenStarted.collect()
                }catch (t: Throwable){
                    loge(t)
                }
            }
        }
    }
    

    【讨论】:

    • 谢谢老兄!我已经坚持了好几个小时了!
    • @Andrew,即使使用 repeatOnLifecycle,它也会在片段进入 STARTED 状态时继续收集,对吧?你是怎么解决的?
    • @Rakesh 正确。不同的是,repeatOnLifecycle 开始收集 onStart,取消收集和 onStop 里面的所有协程。在没有 repeatOnLifecycle 的情况下,开始收集 onStart,并在 onStop 暂停协程。我建议您同时尝试并调试。
    【解决方案2】:

    使用 SharedFlow 并对其应用 replayCache。

    将此共享流的 replayCache 重置为空状态。新订阅者将仅接收此调用后发出的值,而旧订阅者仍将接收先前缓冲的值。要将共享流重置为初始值,请在此调用之后发出该值。 more information

    private val _reorder = MutableSharedFlow<ViewState<ReorderDto?>>().apply {
        resetReplayCache()
    }
    val reorder: SharedFlow<ViewState<ReorderDto?>>
        get() = _reorder
    

    【讨论】:

      猜你喜欢
      • 2020-08-16
      • 2021-06-22
      • 1970-01-01
      • 1970-01-01
      • 2013-10-23
      • 2014-02-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多