从另一个Activity返回时StateFlow未触发Loading()的问题解决
现有两个Activity:Activity Menu和Activity Detail。流程为:进入Activity Menu,点击按钮跳转至Activity Detail,在Activity Detail中更新数据。按下返回键回到Activity Menu时,仅触发了Resource Success状态,Resource Loading状态未被触发。
1. Resource密封类
sealed class Resource<T>( val data: T? = null, val message: String? = null, val messageInt: Int? = null, val code: Int? = null, val errorReader: Reader? = null, ) { class Initialize<T> : Resource<T>() class Loading<T>(data: T? = null) : Resource<T>(data) class Success<T>(data: T?) : Resource<T>(data) class Error<T>( message: String? = null, messageInt: Int? = null, data: T? = null, code: Int? = null, errorReader: Reader? = null ) : Resource<T>(data, message, messageInt, code, errorReader) }
2. ViewModel代码
private val _itemList = MutableStateFlow<Resource<Int>>(Resource.Initialize()) val itemList: StateFlow<Resource<Int>> = _itemList init { viewModelScope.launch { getItemList() } } suspend fun getItemList() = viewModelScope.launch { _itemList.emit(Resource.Loading()) val data = withContext(ioDispatcher + coroutineExceptionHandler) { return@withContext quickSave.getInt(Constant.BROKEN_TOTE_LIST_COUNT, 0) } // 第二次仅触发此Success _itemList.emit(Resource.Success(data = data)) }
3. Activity代码
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityToteBrokenBinding.inflate(layoutInflater) val root = binding.root setContentView(root) startLifeCycle() } private fun startLifeCycle() { lifecycleScope.launch { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { launch { collectItemList() } } } } private suspend fun collectItemList() { viewModel.itemList.collectLatest { when (it) { is Resource.Initialize -> { } is Resource.Loading -> { Log.i("???","Resource Loading") showLoading() } is Resource.Success -> { Log.i("???","Resource Success") handleSuccess() hideLoading() } is Resource.Error -> { hideLoading() } } } }
---------------------------- PROCESS STARTED (30051) for package com.company.com ---------------------------- 2022-09-29 12:21:51.977 30051-30051 ??? com.company.com I Resource Loading 2022-09-29 12:21:52.137 30051-30051 ??? com.company.com I Resource Success 2022-09-29 12:22:10.839 30051-30051 ??? com.company.com I Resource Success
如何实现每次从Activity Detail返回时,都能触发Resource Loading状态?
问题核心是:从Activity Detail返回时,Activity Menu回到STARTED生命周期,但ViewModel的getItemList()未被重新调用,且StateFlow会向新订阅者重放最新的Success状态,因此只会触发Success。要实现每次返回都触发Loading,可按以下方式调整:
1. 主动在Activity回到前台时触发数据刷新
在Activity Menu中重写onResume()方法,主动调用ViewModel的getItemList(),强制重新执行加载流程,先发送Loading再发送Success。
修改后的Activity代码:
override fun onResume() { super.onResume() lifecycleScope.launch { viewModel.getItemList() } }
2. 可选:用SharedFlow替代StateFlow(避免状态重放)
如果不需要StateFlow的状态重放特性,可以将_itemList改为MutableSharedFlow,设置replay = 0,这样新订阅不会收到历史状态,只有主动调用getItemList()时才会发送新的状态流。注意要保证Activity启动时也调用getItemList()。
修改后的ViewModel代码:
private val _itemList = MutableSharedFlow<Resource<Int>>(replay = 0) val itemList: SharedFlow<Resource<Int>> = _itemList // 保持init中的调用,确保首次启动加载数据 init { viewModelScope.launch { getItemList() } }
关键原理
- StateFlow默认会向新订阅者重放最新状态,所以从Detail返回时,Activity重新订阅会直接收到之前的Success,不会触发新的Loading。
- 主动在Activity回到前台时调用
getItemList(),会强制ViewModel重新发送Loading和Success状态,从而实现返回时触发Loading的需求。
内容的提问来源于stack exchange,提问作者Ticherhaz FreePalestine

