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

Flow onEach/collect gets called multiple times when back from Fragment

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

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

问题。然后,当我转到片段 B 然后返回片段 A 时,我的 collect 函数被调用两次。如果我再次进入 Fragment B 并返回到 A - 然后调用 collect 函数 3 次。等等。

原因

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

片段的解决方案 1

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

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

Activity

的解决方案 2

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

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

附加信息

  1. LiveData 也是如此。见 post . Also check 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)
            }
        }
    }
}

使用 SharedFlow 并对其应用 replayCache。

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

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