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

And*_*rew 28 android android-fragments kotlin android-livedata

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

lifecycleScope.launchWhenStarted {
            availableLanguagesFlow.collect {
                languagesAdapter.setItems(it.allItems, it.selectedItem)
            }
        }
Run Code Online (Sandbox Code Playgroud)

问题。然后,当我转到片段 B 然后返回片段 A 时,我的收集函数被调用两次。如果我再次访问片段 B 并返回 A - 那么收集函数将被调用 3 次。等等。

And*_*rew 45

原因

发生这种情况是因为Fragment 生命周期很棘手。当您从片段 B 返回到片段 A 时,片段 A 会重新附加。结果,片段的 onViewCreated 被第二次调用,并且您第二次观察 Flow 的相同实例。换句话说,现在你有一个流和两个观察者,当流发出数据时,其中两个被调用。

片段的解决方案1

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

viewLifecycleOwner.lifecycleScope.launchWhenStarted {
            availableLanguagesFlow.collect {
                languagesAdapter.setItems(it.allItems, it.selectedItem)
            }
        }
Run Code Online (Sandbox Code Playgroud)

活动解决方案 2

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

lifecycleScope.launchWhenStarted {
            availableLanguagesFlow.collect {
                languagesAdapter.setItems(it.allItems, it.selectedItem)
            }
        }
Run Code Online (Sandbox Code Playgroud)

附加信息

  1. LiveData 也会发生同样的情况。请参阅此处的帖子。另请查看这篇文章
  2. 使用 Kotlin 扩展使代码更简洁:

扩大:

fun <T> Flow<T>.launchWhenStarted(lifecycleOwner: LifecycleOwner) {
    lifecycleOwner.lifecycleScope.launchWhenStarted {
        this@launchWhenStarted.collect()
    }
}
Run Code Online (Sandbox Code Playgroud)

在片段 onViewCreated 中:

availableLanguagesFlow
    .onEach {
        //update view
    }.launchWhenStarted(viewLifecycleOwner)
Run Code Online (Sandbox Code Playgroud)

更新

我宁愿使用 now repeatOnLifecycle,因为当生命周期低于状态时(在我的例子中是 onStop ),它会取消正在进行的协程。如果没有repeatOnLifecycle,则在 onStop 时收集将被暂停。看看这篇文章

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

  • 谢谢老兄!我已经为片段卡住了几个小时了! (2认同)