收集SharedFlow时更新UIState无法刷新UI的问题排查
问题分析与解决
当前代码失效的原因
你用的MutableSharedFlow默认replay = 0,这意味着它不会保存历史发射的事件。当ViewModel被销毁重建后,新的ViewModel去订阅这个Flow时,之前login()发射的用户状态已经被丢弃了,所以collect块收不到任何值,自然_uiState不会更新,视图也就没反应。而单例ViewModel的collect一直处于活跃状态,能实时收到后续的发射,所以没问题。
修复当前实现的两种方案
方案1:给SharedFlow设置replay值
修改Repository里的_user定义,让它保存最近1次的发射值,这样新订阅者能拿到历史状态:
private val _user = MutableSharedFlow<User>(replay = 1) override val user: Flow<User> = _user
方案2:在Repository中维护用户状态快照
给Repository加一个变量保存当前用户状态,ViewModel初始化时先直接赋值,再订阅后续变化:
// Repository代码 private val _user = MutableSharedFlow<User>() override val user: Flow<User> = _user var currentUser: User? = null override suspend fun login() { delay(2000) val loggedUser = LoggedUser.aLoggedUser() currentUser = loggedUser _user.emit(loggedUser) } override suspend fun logout() { delay(2000) currentUser = GuestUser _user.emit(GuestUser) } // ViewModel初始化块 init { viewModelScope.launch { // 先赋值当前已有的用户状态 userRepository.currentUser?.let { _uiState.value = UiState.Success(it) } // 再订阅后续变化 userRepository.user.collect { _uiState.value = UiState.Success(it) } // 执行登录(如果需要每次进入屏幕都登录的话,否则放外面) userRepository.login() } }
全局用户状态的最优实现
用户状态属于全局持续状态,更适合用StateFlow(它是专门用于表示状态的热流,自带初始值,且会保存当前状态)。改造后的Repository代码如下:
// Repository代码 private val _user = MutableStateFlow<User>(GuestUser) // 设初始值为访客状态 override val user: StateFlow<User> = _user override suspend fun login() { delay(2000) _user.value = LoggedUser.aLoggedUser() // StateFlow用value赋值,不需要emit } override suspend fun logout() { delay(2000) _user.value = GuestUser }
对应的ViewModel可以简化:
init { viewModelScope.launch { userRepository.login() // 订阅StateFlow,新订阅者会立即拿到当前的用户状态 userRepository.user.collect { _uiState.value = UiState.Success(it) } } }
这种方式下,不管ViewModel什么时候创建,订阅user流都会立即拿到当前的用户状态,完美满足你“导航到屏幕时感知最新用户状态”的需求。
内容的提问来源于stack exchange,提问作者Lheonair
相关产品推荐
相关产品推荐

