资讯动态

【Jetpack Compose进阶学与练】第4课:ViewModel结合Navigation导航;不同页面之间共享ViewModel

发布时间:2026/10/9 2:38:23 来源:尧图企业网站定制
前言上一课学习了基础ViewModel默认情况下每一个导航页面composable目的地各自拥有独立的ViewModel实例。如果希望多个导航页面之间共享同一份ViewModel例如多页表单A页录入、B页继续编辑就不能直接在页面内部调用viewModel()。本节课学习Nav‑BackStackEntry的ViewModelStore实现导航作用域下共享ViewModel。知识点清单默认行为每个composable页面各自隔离ViewModel实例NavGraph导航图作用域共享ViewModel整个导航范围之内单例获取导航回退栈entry拿到ViewModelStoreOwner基于该owner获取共享ViewModel多页面表单示例录入页A预览页B共享一份表单ViewModel共享ViewModel的销毁时机整个导航回退栈全部弹出之后才销毁完整示例源码importandroidx.lifecycle.ViewModelimportandroidx.lifecycle.viewModelScopeimportkotlinx.coroutines.flow.MutableStateFlowimportkotlinx.coroutines.flow.StateFlowimportkotlinx.coroutines.launchimportandroidx.compose.runtime.Composableimportandroidx.compose.runtime.collectAsStateWithLifecycleimportandroidx.compose.runtime.getValueimportandroidx.navigation.NavHostControllerimportandroidx.navigation.compose.NavHostimportandroidx.navigation.compose.composableimportandroidx.navigation.compose.rememberNavControllerimportandroidx.lifecycle.viewmodel.compose.viewModelobjectRoute{constvalFORM_INPUTform_inputconstvalFORM_PREVIEWform_preview}classFormShareViewModel:ViewModel(){privateval_nameFlowMutableStateFlow()valnameFlow:StateFlowString_nameFlowprivateval_phoneFlowMutableStateFlow()valphoneFlow:StateFlowString_phoneFlowfunupdateName(text:String){viewModelScope.launch{_nameFlow.valuetext}}funupdatePhone(text:String){viewModelScope.launch{_phoneFlow.valuetext}}}ComposablefunShareVmNavPage(){valnavControllerrememberNavController()NavHost(navControllernavController,startDestinationRoute.FORM_INPUT){composable(Route.FORM_INPUT){backStackEntry-//拿到导航图的根entry作为ViewModelStoreOwner实现共享valparentEntryremember(backStackEntry){navController.getBackStackEntry(Route.FORM_INPUT)}valsharedVm:FormShareViewModelviewModel(viewModelStoreOwnerparentEntry)FormInputScreen(navController,sharedVm)}composable(Route.FORM_PREVIEW){backStackEntry-valparentEntryremember(backStackEntry){navController.getBackStackEntry(Route.FORM_INPUT)}valsharedVm:FormShareViewModelviewModel(viewModelStoreOwnerparentEntry)FormPreviewScreen(navController,sharedVm)}}}ComposablefunFormInputScreen(navController:NavHostController,vm:FormShareViewModel){valnamebyvm.nameFlow.collectAsStateWithLifecycle()valphonebyvm.phoneFlow.collectAsStateWithLifecycle()androidx.compose.foundation.layout.Column(modifierandroidx.compose.ui.Modifier.fillMaxSize(),verticalArrangementandroidx.compose.foundation.layout.Arrangement.Center,horizontalAlignmentandroidx.compose.ui.Alignment.CenterHorizontally){androidx.compose.material3.Text(text表单录入页)androidx.compose.material3.TextField(valuename,onValueChange{vm.updateName(it)},placeholder{androidx.compose.material3.Text(请输入姓名)})androidx.compose.material3.TextField(valuephone,onValueChange{vm.updatePhone(it)},placeholder{androidx.compose.material3.Text(请输入手机号)})androidx.compose.material3.Button(onClick{navController.navigate(Route.FORM_PREVIEW)}){androidx.compose.material3.Text(前往预览页面)}}}ComposablefunFormPreviewScreen(navController:NavHostController,vm:FormShareViewModel){valnamebyvm.nameFlow.collectAsStateWithLifecycle()valphonebyvm.phoneFlow.collectAsStateWithLifecycle()androidx.compose.foundation.layout.Column(modifierandroidx.compose.ui.Modifier.fillMaxSize(),verticalArrangementandroidx.compose.foundation.layout.Arrangement.Center,horizontalAlignmentandroidx.compose.ui.Alignment.CenterHorizontally){androidx.compose.material3.Text(text表单预览页读取共享ViewModel的数据)androidx.compose.material3.Text(text姓名$name)androidx.compose.material3.Text(text手机号$phone)androidx.compose.material3.Button(onClick{navController.popBackStack()}){androidx.compose.material3.Text(返回录入页)}}}逐行解读默认隔离行为如果直接在每个Screen内部调用 viewModel() A页面、B页面会各自生成独立FormShareViewModel对象A录入的数据B页面读取不到数据隔离。getBackStackEntry 获取指定路由的BackStackEntry作为ViewModelStoreOwnervalparentEntryremember(backStackEntry){navController.getBackStackEntry(Route.FORM_INPUT)}valsharedVm:FormShareViewModelviewModel(viewModelStoreOwnerparentEntry)viewModelStoreOwner parentEntry 指定ViewModel的存储所有者是FORM_INPUT这条回退栈条目A页面与B页面都从同一个owner获取ViewModel于是拿到同一个实例实现数据共享。共享ViewModel销毁时机非常关键ViewModel的生命周期绑定 FORM_INPUT 这条回退栈条目只要FORM_INPUT还保留在回退栈里面ViewModel就存活当FORM_INPUT页面从回退栈中被弹出之后共享ViewModel立刻执行clear()销毁释放内存。适合分步多页表单当用户退出表单流程表单数据自动销毁不需要手动清空。业务数据流录入页修改Flow预览页直接收集Flow不需要页面之间传递Bundle参数多页表单的数据全部保存在共享ViewModel内部。和全局单例的区别不要自己写object单例类来实现页面共享数据导航作用域的共享ViewModel会在退出表单流程时自动销毁全局object会常驻内存残留旧表单数据容易产生bug。容易踩坑❌ 在子页面直接无参调用viewModel()得到独立实例无法共享数据。❌ 使用object全局单例做页面间数据共享退出流程数据不会自动清空残留脏数据。❌ 忘记remember{}包裹getBackStackEntry重组的时候反复获取entry造成异常。✅ 共享ViewModel销毁时机绑定指定路由栈条目该条目出栈ViewModel随即销毁。多元化习题练习习题1【基础‑改错题】题目多页表单A录入B预览直接在两个页面分别无参调用viewModel()会出现什么问题valvm:FormShareViewModelviewModel()答案会生成两份独立ViewModel实例A页面录入的数据保存到A的ViewModelB页面拿到的是全新ViewModelB读取不到A录入的表单数据。解读默认每个composable目的地是独立ViewModelStoreOwner实例互相隔离想要共享必须指定同一个ViewModelStoreOwnerBackStackEntry。习题2【基础‑代码修改题】现有路由 STEP_ONE“step_one” STEP_TWO“step_two” 两个页面希望共享ViewModel写出获取parentEntry以及共享vm的代码片段。答案valparentEntryremember(backStackEntry){navController.getBackStackEntry(Route.STEP_ONE)}valsharedVm:FormShareViewModelviewModel(viewModelStoreOwnerparentEntry)解读统一以第一步页面的回退栈条目作为owner两个页面都从该owner取出ViewModel实现实例唯一。习题3【进阶‑补充代码题】题目多页表单完成提交之后希望直接清除整个表单流程提交之后回退栈把step_one弹出返回直接回到首页写出跳转并且清理回退栈的点击逻辑。答案navController.navigate(Route.HOME){popUpTo(Route.STEP_ONE){inclusivetrue}}解读popUpTo并且inclusivetrue把step_one这条栈条目弹出绑定于该entry的共享ViewModel会自动clear销毁表单临时数据随之释放。习题4【综合‑逻辑应用题】题目对比「导航作用域共享ViewModel」与「手写object全局单例」说明导航共享ViewModel优势。答案导航作用域共享ViewModel生命周期绑定导航回退栈条目当该栈条目被弹出ViewModel自动销毁临时表单数据自动释放object全局单例常驻内存上一次表单的数据会一直残留下一次打开表单会读到脏旧数据必须手动清空容易遗漏。解读页面流程内临时共享优先选择导航scope共享ViewModel尽量避免手写业务全局单例。习题5【概念简答题】题目共享导航ViewModel什么时候销毁答案共享ViewModel的生命周期绑定用于充当owner的那条回退栈条目只要该路由条目还保留在回退栈ViewModel就存活一旦这条回退栈条目被弹出popUpTo‑inclusive或者回退返回ViewModel执行clear()销毁内部viewModelScope协程全部取消。解读生命周期跟随导航栈而不是跟随Activity这是导航共享ViewModel最核心特点。本节课知识点总结默认每一个composable导航页面拥有隔离独立ViewModel实例数据互相隔离。通过 navController.getBackStackEntry(route) 拿到指定路由的BackStackEntry作为ViewModelStoreOwner多个页面以此owner获取ViewModel得到同一个实例完成页面间数据共享。共享ViewModel生命周期绑定对应的回退栈条目条目出栈ViewModel自动销毁释放资源。适合分步多页表单业务场景优先导航scope共享ViewModel禁止使用object全局单例保存页面临时业务数据。使用remember{}包裹getBackStackEntry防止重组反复获取回退栈条目引发异常。下一课预告进阶第5课Flow基础StateFlow与SharedFlow区别在ViewModel中处理网络请求

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑