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

如何仅在Jetpack Compose NavGraph组件间共享ViewModel

报错原因

navigation 构造块本身不属于 @Composable 作用域,仅 composable(路由) { } 内部的代码块才是Composable执行环境,你直接在navigation块内调用viewModel()这个Composable函数,自然会触发@Composable invocations can only happen from the context of a @Composable function编译错误。

解决方案

要实现ViewModel绑定当前NavGraph的生命周期、同导航图下多个Composable共享、离开导航图自动销毁的需求,只需要在每个子页面获取ViewModel时,指定其ViewModelStoreOwner为当前导航图的BackStackEntry即可。

完整示例代码

setContent {
    val navController = rememberNavController()

    NavHost(navController = navController, startDestination = "home") {
        navigation(startDestination = "username", route = "login") {
            composable("username") {
                // 获取login导航图对应的BackStackEntry
                val loginNavEntry = remember { navController.getBackStackEntry("login") }
                // 绑定导航图的StoreOwner获取ViewModel
                val viewModel: LoginViewModel = viewModel(viewModelStoreOwner = loginNavEntry)
                // 页面业务逻辑
            }
            composable("password") {
                val loginNavEntry = remember { navController.getBackStackEntry("login") }
                val viewModel: LoginViewModel = viewModel(viewModelStoreOwner = loginNavEntry)
                // 页面业务逻辑
            }
            composable("registration") {
                val loginNavEntry = remember { navController.getBackStackEntry("login") }
                val viewModel: LoginViewModel = viewModel(viewModelStoreOwner = loginNavEntry)
                // 页面业务逻辑
            }
        }
        // 其他路由定义
    }
}

简化写法

如果觉得重复代码太多,可以封装成通用的Composable扩展函数:

@Composable
fun NavBackStackEntry.getScopedViewModel<T: ViewModel>(
    navController: NavController,
    graphRoute: String
): T {
    val graphEntry = remember { navController.getBackStackEntry(graphRoute) }
    return viewModel(viewModelStoreOwner = graphEntry)
}

在子页面中直接调用即可:

composable("username") { entry ->
    val viewModel = entry.getScopedViewModel<LoginViewModel>(navController, "login")
}

Hilt用户适配

如果你使用Hilt注入ViewModel,只需把viewModel()替换为hiltViewModel()即可,参数逻辑完全一致:

val viewModel: LoginViewModel = hiltViewModel(loginNavEntry)

生命周期符合说明

  • 同导航图下所有子页面获取到的都是同一个ViewModel实例,可共享状态
  • 当你离开login导航图(回退到home或跳转到其他非login子路由)时,login导航图对应的BackStackEntry会被系统销毁,绑定在其上的ViewModel也会同步被回收,不会内存泄漏
  • 下次重新进入login导航图时会生成新的BackStackEntry,自动初始化全新的ViewModel实例,完全匹配你的需求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 00:45:01