从 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 添加数据并展示