从 Fragment 返回时,Flow onEach/collect 被多次调用

Posted

技术标签:

【中文标题】从 Fragment 返回时,Flow onEach/collect 被多次调用【英文标题】:Flow onEach/collect gets called multiple times when back from Fragment 【发布时间】: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 次。以此类推。

【问题讨论】:

【参考方案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)
            
        

其他信息

    LiveData 也是如此。见帖子here。另请查看this article。 使用 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

【讨论】:

以上是关于从 Fragment 返回时,Flow onEach/collect 被多次调用的主要内容,如果未能解决你的问题,请参考以下文章

(导航组件)返回首页fragment时如何在activity上显示返回箭头?

如何从 Main Activity 到 Fragment,并从同一个 Fragment 返回到 Main Activity?

Android Kotlin Room 与Flow的应用 demo 添加数据并展示

从后台弹出时不调用 Fragment 的 onResume()

使用导航架构操作点击返回按钮时如何避免片段重新创建?

从画廊返回后,不在 Fragment 中调用 onActivityResult