You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 04:32:49