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

如何在Jetpack Compose嵌套导航图的父屏幕中共享子导航图屏幕的ViewModel

如何在Jetpack Compose嵌套导航图的父屏幕中共享子导航图屏幕的ViewModel

我明白你现在的困境——在嵌套导航结构里,想让父屏幕拿到子导航图里ViewModel的同一个实例,还要避开全局ViewModel和导航子图的限制。其实我们可以通过共享嵌套导航控制器的引用,再结合Hilt ViewModel的重载方法来实现,具体步骤如下:

步骤1:创建共享导航状态的ViewModel

我们需要一个中间ViewModel来持有嵌套导航控制器的引用,这样主导航里的HomeScreen就能拿到它:

@HiltViewModel
class NavSharedViewModel @Inject constructor() : ViewModel() {
    // 用MutableState来保存嵌套NavController的引用
    val nestedNavController = mutableStateOf<NavController?>(null)
}

步骤2:在嵌套容器屏幕中传递导航控制器引用

在NestedContainerScreen里,创建好嵌套导航控制器后,把它赋值给刚才的共享ViewModel:

@Composable
fun NestedContainerScreen(navController: NavHostController) {
    val nestedNavController = rememberNavController()
    val navSharedViewModel: NavSharedViewModel = hiltViewModel()

    // 当嵌套导航控制器初始化完成后,把它存入共享ViewModel
    LaunchedEffect(nestedNavController) {
        navSharedViewModel.nestedNavController.value = nestedNavController
    }

    NavHost(navController = nestedNavController, startDestination = "nested_home") {
        composable("nested_home") {
            NestedHomeScreen(nestedNavController)
        }
        composable("nested_detail/{id}") { backStackEntry ->
            val id = backStackEntry.arguments?.getString("id")
            NestedDetailScreen(id = id)
        }
    }
}

步骤3:在父屏幕中获取子导航图的ViewModel

在HomeScreen里,先从共享ViewModel拿到嵌套导航控制器,再通过它获取nested_home页面的BackStackEntry,最后用这个Entry作为ViewModelStoreOwner来获取同一个HomeViewModel实例:

@Composable
fun HomeScreen(navController: NavHostController) {
    val navSharedViewModel: NavSharedViewModel = hiltViewModel()
    val nestedNavController = navSharedViewModel.nestedNavController.value

    // 安全获取子导航图的ViewModel:需要确保nested_home已经在导航栈中
    val nestedHomeViewModel = nestedNavController?.let { nav ->
        runCatching {
            // 获取nested_home对应的BackStackEntry
            nav.getBackStackEntry("nested_home")
        }.getOrNull()?.let { backStackEntry ->
            // 传入BackStackEntry,获取和NestedHomeScreen相同的ViewModel实例
            hiltViewModel<HomeViewModel>(backStackEntry)
        }
    }

    // 实际使用时要处理null的情况(比如还没导航到nested_home的时候)
    nestedHomeViewModel?.let { viewModel ->
        // 这里可以正常访问viewModel的状态和方法了
        Text("子ViewModel的某状态:${viewModel.someState}")
    } ?: run {
        Text("还未加载子页面的ViewModel")
    }
}

为什么这个方案符合你的要求?

  • 没有使用全局scope的ViewModel到父导航宿主:我们只是用了一个共享ViewModel来传递导航控制器的引用,HomeViewModel仍然和nested_home的BackStackEntry绑定,生命周期完全由子导航栈管理。
  • 没有使用导航组件的subgraph:我们保持了你原本的嵌套NavHost结构,没有修改导航图的定义方式。

注意事项

  • 当nested_home从嵌套导航栈中弹出时,对应的HomeViewModel会被销毁,此时HomeScreen里的nestedHomeViewModel会变成null,记得做好空值处理。
  • 确保在访问nestedHomeViewModel时,nested_home已经被导航过(也就是嵌套导航栈里存在这个页面),否则getBackStackEntry会抛出异常,我们用runCatching来捕获这种情况。

备注:内容来源于stack exchange,提问作者Santhosh Kumar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.15 10:08:07