使用repeatOnLifecycle时StateFlow重复收集及LiveData式实现
在使用repeatOnLifecycle收集StateFlow时,常会遇到这个问题:跳转到其他Activity再返回原页面,就算StateFlow的值没更新,收集逻辑还是会重新执行。比如下面的例子:
ViewModel 代码
private var _deletionStatusStateFlow = MutableStateFlow(0) val deletionStatusStateFlow = _deletionStatusStateFlow.asStateFlow()
Fragment 收集代码
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED){ deleteAccountViewModel.deletionStatusStateFlow.collect { if (it == 1){ startActivity(AnyActivity) } } } }
每次从AnyActivity返回,都会重复打开这个页面,但换成LiveData实现相同逻辑时,返回后就不会触发重复执行。下面分场景给出解决方法:
一、简单场景:过滤重复发射值
对于单一状态的StateFlow,直接用distinctUntilChanged()过滤掉和上一次相同的发射值就行。修改Fragment的收集代码:
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED){ deleteAccountViewModel.deletionStatusStateFlow .distinctUntilChanged() // 只处理值真正变化的情况 .collect { if (it == 1){ startActivity(AnyActivity) } } } }
原理是StateFlow在订阅时会立即发射当前值,distinctUntilChanged()会忽略重复的相同值,这样返回页面时因为值没变化,就不会触发后续逻辑,和LiveData的行为一致。
二、复杂场景:避免上游流重复创建
对于包含mapLatest这类依赖上游逻辑的StateFlow,比如下面的搜索场景:
val results: StateFlow<SearchResult> = queryFlow.mapLatest { query -> repository.search(query) }.stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000L), initialValue = SearchResult.EMPTY )
如果直接用distinctUntilChanged(),当页面回到STARTED状态重新订阅时,若订阅者消失超过5秒,StateFlow会停止上游流,再次订阅会重新触发repository.search(query),造成额外的仓库读取开销。可以通过以下方式解决:
1. 调整共享策略的等待时间
把WhileSubscribed的等待时间延长,避免短时间内因为订阅者暂时消失就停止流:
val results: StateFlow<SearchResult> = queryFlow.mapLatest { query -> repository.search(query) }.stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(10000L), // 延长到10秒 initialValue = SearchResult.EMPTY )
2. 结合distinctUntilChanged()过滤重复结果
在收集时添加distinctUntilChanged(),确保只有搜索结果真正变化时才更新UI:
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED){ viewModel.results .distinctUntilChanged() .collect { searchResult -> // 更新搜索结果UI } } }
3. 自定义永久活跃的共享策略
如果业务允许流一直保持活跃,直到ViewModel销毁,可以自定义SharingStarted实现:
val foreverStarted = SharingStarted { _, _ -> SharingStarted.Eagerly.subscriptionCount } val results: StateFlow<SearchResult> = queryFlow.mapLatest { query -> repository.search(query) }.stateIn( scope = viewModelScope, started = foreverStarted, initialValue = SearchResult.EMPTY )
这样流不会因为订阅者暂时消失而重新创建,从根本上避免了额外的仓库请求。
内容的提问来源于stack exchange,提问作者Mohmmaed-Amleh

