Jetpack Compose页面被NavHost包裹后无法收集同页面状态如何解决?
异常产生原因
问题核心是Hilt ViewModel的作用域绑定对象发生了变化:
- 未引入NavHost时,
ScreenSignInWithInputValidationDebounce中hiltViewModel()默认查找最近的ViewModelStoreOwner,也就是当前ActAuth实例,和Activity中通过by viewModels()声明的是完全相同的ViewModel实例,所以状态可以同步。 - 引入NavHost后,NavHost中每个
composable路由节点的默认ViewModelStoreOwner是对应路由的NavBackStackEntry,此时页面中调用hiltViewModel()会生成/获取绑定到当前路由栈条目的ViewModel实例,和Activity中绑定的ViewModel完全是两个不同的实例,两边收集的stateAuth属于不同对象,自然无法同步状态。
修复方案
可根据实际业务需求选择以下任意一种方案:
- 显式指定页面ViewModel的绑定宿主为Activity
在NavHost的路由构造中,显式将Activity作为ViewModelStoreOwner传入,保证页面和Activity拿到同一个ViewModel实例:
composable(NavRoutesAuth.SignIn.route) { val activity = LocalContext.current as AppCompatActivity ScreenSignInWithInputValidationDebounce( navController, viewModel = hiltViewModel(activity) ) } // 注册页面如果需要共享ViewModel也做同样处理
- 将ViewModel绑定到当前NavGraph作用域
如果仅需要登录、注册两个页面共享ViewModel,不需要和Activity耦合,可以将ViewModel绑定到整个Auth导航图上:
@Composable fun NavHostAuth(navController: NavHostController) { // 获取整个Auth导航图的ViewModelStoreOwner val navGraphStoreOwner = remember(navController) { // 替换为你的导航图ID或者根路由即可 navController.getBackStackEntry(NavRoutesAuth.SignIn.route) } NavHost( navController = navController, startDestination = NavRoutesAuth.SignIn.route ) { composable(NavRoutesAuth.SignIn.route) { ScreenSignInWithInputValidationDebounce( navController, viewModel = hiltViewModel(navGraphStoreOwner) ) } composable(NavRoutesAuth.SignUp.route) { ScreenSignUpWithInputValidationDebounce( navController, viewModel = hiltViewModel(navGraphStoreOwner) ) } } }
- 调整状态收集的位置
如果不需要在Activity侧处理状态逻辑,可以将原来Activity中的状态收集逻辑挪到对应页面中,使用LaunchedEffect实现收集,无需修改ViewModel绑定逻辑:
@Composable fun ScreenSignInWithInputValidationDebounce( viewModel: ViewModelAuth = hiltViewModel() ){ LaunchedEffect(Unit) { viewModel.stateAuth.collect { state -> // 原来在Activity中的处理逻辑挪到这里 } } // 原有页面逻辑 }
内容的提问来源于stack exchange,提问作者Leon
相关产品推荐
相关产品推荐

