如何将SingleLiveEvent<Unit>重构为MutableSharedFlow?导航未触发原因排查
核心原因分析
1. SharedFlow默认不缓存历史事件
SingleLiveEvent基于LiveData实现,会保存最新的事件值,当Fragment后续订阅时,能收到订阅前发送的事件。但默认的MutableSharedFlow<Unit>参数为:
replay = 0:不缓存任何历史事件extraBufferCapacity = 0:没有额外缓冲区
如果_trigger.call()(即tryEmit(Unit))在Fragment开始collect之前执行,这个事件会直接丢失,后续的collect无法获取到它。
2. tryEmit可能发送失败
tryEmit是非挂起函数,当Flow没有活跃的收集者、且缓冲区已满时,会直接返回false,事件发送失败。而SingleLiveEvent的call()本质是调用LiveData的setValue,无论是否有观察者都会保存事件。
解决方案
方案1:设置replay缓存(适配粘性场景)
如果需要和SingleLiveEvent一样,让后续订阅的观察者能收到之前发送的事件,初始化MutableSharedFlow时设置replay = 1:
// ViewModel中 private var _trigger = MutableSharedFlow<Unit>(replay = 1) val trigger = _trigger.asSharedFlow() // 发送事件(可保留原扩展函数,或直接调用tryEmit) fun MutableSharedFlow<Unit>.call() = tryEmit(Unit) // 调用处 _trigger.call()
这样即使事件在collect前发送,后续订阅的collect会收到replay的缓存事件。
方案2:增加缓冲区容量(非粘性场景)
如果不需要粘性事件,只是希望在没有活跃收集者时临时缓存事件,可设置extraBufferCapacity = 1:
private var _trigger = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
这样当没有收集者时,tryEmit能将事件存入缓冲区,等收集者开始collect时再发送。
方案3:改用挂起函数emit(确保事件发送)
如果希望事件一定被发送(即使需要等待收集者),可使用挂起函数emit,但需要在协程中调用:
// ViewModel中 private var _trigger = MutableSharedFlow<Unit>() val trigger = _trigger.asSharedFlow() // 发送事件(在viewModelScope协程中) viewModelScope.launch { _trigger.emit(Unit) }
emit会挂起直到事件被收集,避免事件丢失,但需要确保调用处处于协程环境。
优化Fragment收集代码
原代码中嵌套的launch是多余的,可简化为:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.trigger.collect { // 执行导航操作 } } } }
内容的提问来源于stack exchange,提问作者Raoul
相关产品推荐
相关产品推荐

