Kotlin Compose中登出时异步清理本地存储后导航的实现方案
解决Compose登出时数据未清理完成就跳转的问题
你的核心问题是登出逻辑执行顺序错误:点击确认后,你先启动了清理数据的协程,但不等它执行完毕就立刻触发导航,导致数据清理还没完成就跳走了。用runBlocking会阻塞UI线程,肯定不可取,正确的做法是等待清理操作完成后再触发导航。
方案1:直接修正Screen中的协程逻辑
把更新状态(关闭弹窗、触发导航)的代码放到协程内部,确保清理完成后再执行:
if (state.shouldShowSignOutPopUp) { SignOutPopUp( isClickedYes = { coroutineScope.launch { // 先等待所有清理操作完成 cleanLocalStorage() // 清理完毕后再更新状态、触发导航 shouldSignOut(true) shouldShowSignOutPopUp(false) } }, isClickedNo = { shouldSignOut(false) shouldShowSignOutPopUp(false) }, text = "Çıkış yapmak istediğinize emin misiniz ?" ) }
协程会先挂起执行cleanLocalStorage,等所有异步清理操作完成后,再执行后面的状态更新,导航就会在数据清理完毕后触发。
方案2:将登出逻辑封装到ViewModel(更符合MVVM)
把登出的完整逻辑移到ViewModel中,让UI只负责触发事件,ViewModel处理异步操作和状态更新,代码更清晰易维护:
修改后的ViewModel
@HiltViewModel class SettingsViewModel @Inject constructor( private val tokenManager: TokenManager, private val preferenceDataStoreHelper: PreferenceDataStoreHelper ) : ViewModel() { // 用StateFlow管理UI状态(适配你原有的状态管理逻辑) private val _shouldSignOut = MutableStateFlow(false) val shouldSignOut = _shouldSignOut.asStateFlow() private val _shouldShowSignOutPopUp = MutableStateFlow(false) val shouldShowSignOutPopUp = _shouldShowSignOutPopUp.asStateFlow() suspend fun cleanLocalStorage() { tokenManager.deleteAccessToken() tokenManager.deleteRefreshToken() preferenceDataStoreHelper.removePreference(PreferenceDataStoreConstants.USER_EMAIL) preferenceDataStoreHelper.removePreference(PreferenceDataStoreConstants.USER_NAME) preferenceDataStoreHelper.removePreference(PreferenceDataStoreConstants.IS_USER_CHOOSE_CM) preferenceDataStoreHelper.removePreference(PreferenceDataStoreConstants.IS_USER_CHOOSE_FT) } // 封装登出确认逻辑 fun handleSignOutConfirm() { viewModelScope.launch { cleanLocalStorage() // 清理完成后更新状态 _shouldSignOut.emit(true) _shouldShowSignOutPopUp.emit(false) } } // 封装登出取消逻辑 fun handleSignOutCancel() { _shouldSignOut.emit(false) _shouldShowSignOutPopUp.emit(false) } // 重置导航状态,避免重复触发 fun resetSignOutState() { _shouldSignOut.emit(false) } }
修改后的Screen
// 收集ViewModel的状态 val shouldSignOut by viewModel.shouldSignOut.collectAsState() val shouldShowSignOutPopUp by viewModel.shouldShowSignOutPopUp.collectAsState() LaunchedEffect(key1 = shouldSignOut) { if (shouldSignOut) { navHostController.navigate(RegisterScreen.SignUpStartRegisterScreen.route) // 导航后重置状态,防止重复触发 viewModel.resetSignOutState() } } if (shouldShowSignOutPopUp) { SignOutPopUp( isClickedYes = { viewModel.handleSignOutConfirm() }, isClickedNo = { viewModel.handleSignOutCancel() }, text = "Çıkış yapmak istediğinize emin misiniz ?" ) }
关键说明
- 绝对不要在UI线程使用
runBlocking,会导致界面卡顿甚至ANR。 - 异步操作必须等执行完成后再走后续逻辑,协程的挂起特性恰好解决这个问题,无需额外回调。
- 将业务逻辑封装到ViewModel是MVVM的最佳实践,UI只负责展示和触发事件,避免UI层混杂业务逻辑。
内容的提问来源于stack exchange,提问作者NewPartizal
相关产品推荐
相关产品推荐

