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

Jetpack Compose中带ViewModel协程的页面导航正确实现方式

问题解决:ViewModel协程因页面导航销毁未执行的问题

核心原因

点击完成按钮后,先执行navHostController.popBackStack()和navigate操作,直接将WelcomeScreen从导航栈移除,导致关联的WelcomeScreenViewModel被销毁。由于viewModelScope是生命周期感知组件,在ViewModel的onClear()方法调用时会取消所有关联协程,保存状态的协程还未启动就被终止。

可行解决方案

方案1:用NonCancellable确保保存操作完成

在ViewModel的协程中,将保存逻辑包裹在withContext(NonCancellable)中,即使ViewModel被销毁,这部分代码也能执行完毕。

修改后的WelcomeScreenViewModel代码:

@HiltViewModel
class WelcomeScreenViewModel @Inject constructor(private val useCase: UseCase): ViewModel(){

    private val TAG = "WelcomeScreenViewModel"

    fun welcomePageCompleted(isCompleted: Boolean){
        viewModelScope.launch(Dispatchers.IO) {
            try {
                withContext(NonCancellable) {
                    useCase.welcomePageCompletedUseCase(isCompleted = isCompleted)
                    Log.i(TAG, "Coroutine triggered! ")
                    Log.i(TAG,"welcome page state saved! $isCompleted")
                }
            } finally {
                // 可选:添加清理逻辑
            }
        }
    }
}

同时调整按钮点击逻辑的顺序,先调用ViewModel的保存方法,再执行导航:

FinishButton(
    modifier = Modifier.weight(1f),
    pagerState = pagerState,
    lastPage = pages.size - 1
) {
    welcomeViewModel.welcomePageCompleted(isCompleted = true)
    navHostController.popBackStack()
    navHostController.navigate(Screen.Home.route)
}

方案2:通过Flow通知UI导航(更推荐)

让ViewModel在保存完成后通知UI执行导航,确保保存操作完全完成后再销毁页面。

修改ViewModel,添加Flow用于导航通知:

@HiltViewModel
class WelcomeScreenViewModel @Inject constructor(private val useCase: UseCase): ViewModel(){

    private val TAG = "WelcomeScreenViewModel"
    private val _navigateToHome = MutableSharedFlow<Unit>()
    val navigateToHome = _navigateToHome.asSharedFlow()

    fun welcomePageCompleted(isCompleted: Boolean){
        viewModelScope.launch(Dispatchers.IO) {
            useCase.welcomePageCompletedUseCase(isCompleted = isCompleted)
            Log.i(TAG, "Coroutine triggered! ")
            Log.i(TAG,"welcome page state saved! $isCompleted")
            // 保存完成后发送导航信号
            _navigateToHome.emit(Unit)
        }
    }
}

在WelcomeScreen中收集Flow并执行导航:

@Composable
fun WelcomeScreen(
    navHostController: NavHostController,
    welcomeViewModel: WelcomeScreenViewModel = hiltViewModel()
) {
    // 原有代码保持不变...

    // 监听导航通知
    LaunchedEffect(Unit) {
        welcomeViewModel.navigateToHome.collect {
            navHostController.popBackStack()
            navHostController.navigate(Screen.Home.route)
        }
    }

    FinishButton(
        modifier = Modifier.weight(1f),
        pagerState = pagerState,
        lastPage = pages.size - 1
    ) {
        // 仅触发保存操作,导航由Flow通知执行
        welcomeViewModel.welcomePageCompleted(isCompleted = true)
    }
}

之前尝试无效的原因

  1. 用rememberCoroutineScope()包裹导航:该Scope属于Composable的生命周期,页面被移除时Scope会被取消,导致导航操作失效。
  2. 先调用ViewModel方法再导航:虽然顺序正确,但ViewModel协程启动需要时间,导航操作立即执行导致ViewModel被销毁,协程仍会被取消;而NonCancellable或Flow通知的方式能规避这个问题。

内容的提问来源于stack exchange,提问作者Abdelaziz Faki

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 14:54:09