如何在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
相关产品推荐
相关产品推荐

