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

Jetpack Navigation中SavedStateHandle ViewModel跨导航及栈清除状态不保留问题

问题分析与解决方案

状态无法保留的原因

  • ViewModel生命周期与页面强绑定:你通过koinNavViewModel()获取的SecondViewModel默认绑定到ScreenSecond对应的NavBackStackEntry生命周期。当调用navigateWithClearStack清除回退栈时,ScreenSecond的NavBackStackEntry会被销毁,关联的ViewModel也会被回收,导致SavedStateHandle中的状态随之丢失。
  • SavedStateHandle作用范围限制:SavedStateHandle的状态仅在其绑定的ViewModel生命周期内存活,一旦ViewModel被销毁,状态会被清除,无法跨页面跳转保留。

解决方法

要保留状态,需让SecondViewModel脱离单个页面的生命周期,绑定到更上层的导航容器(比如整个NavHost的父路由),这样即使页面被弹出回退栈,ViewModel仍能存活,SavedStateHandle中的状态也会保留。

修改后的代码示例

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.SavedStateHandleSaveableApi
import androidx.lifecycle.viewmodel.compose.saveable
import androidx.navigation.NavController
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import org.koin.androidx.compose.navigation.koinNavViewModel

class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            NavigationScreen()
        }
    }
}

@Composable
fun NavigationScreen(navController: NavHostController = rememberNavController()) {
    NavHost(
        navController = navController,
        startDestination = "ScreenFirst",
        route = "parentRoute"
    ) {

        composable("ScreenFirst") {
            Button(onClick = {
                navController.navigateWithClearStack("ScreenSecond")
            }) {
                Text(text = "跳转到第二个页面")
            }
        }

        composable("ScreenSecond") {
            // 将ViewModel绑定到父路由"parentRoute"的生命周期
            val viewModel: SecondViewModel = koinNavViewModel(route = "parentRoute")

            Column(modifier = Modifier.fillMaxSize()) {
                Button(onClick = { viewModel.update(true) }) {
                    Text(text = "显示返回按钮")
                }

                AnimatedVisibility(visible = viewModel.isBackButtonVisible) {
                    Button(onClick = {
                        navController.navigateWithClearStack("ScreenFirst")
                    }) {
                        Text(text = "返回第一个页面")
                    }
                }
            }
        }
    }
}

fun NavController.navigateWithClearStack(screenRoute: String) {
    this.navigate(screenRoute) {
        popUpTo(currentBackStackEntry?.destination?.route ?: return@navigate) {
            inclusive = true
        }
        launchSingleTop = true
    }
}

class SecondViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {

    @OptIn(SavedStateHandleSaveableApi::class)
    var isBackButtonVisible by savedStateHandle.saveable { mutableStateOf(false) }
        private set

    fun update(value: Boolean) {
        isBackButtonVisible = value
    }
}

关键修改点

  • 获取ViewModel时,使用koinNavViewModel(route = "parentRoute"),将ViewModel的生命周期绑定到整个NavHost的父路由,而非ScreenSecond单个页面。这样即使ScreenSecond被弹出回退栈,ViewModel仍会保留,SavedStateHandle中的状态也不会丢失。
  • 确保NavHost的route参数设置为parentRoute,让Koin可以正确识别这个父作用域。

内容的提问来源于stack exchange,提问作者Compose Learner

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 07:51:01