前言
上一课学习了基础跳转与回退栈。本节课学习正向页面传参以及从下一级页面返回数据给上一级页面(页面返回结果)。
重点:Navigation‑Compose不建议传递复杂对象;优先传递id、字符串、数字这类基础类型;复杂对象通过id去仓库读取数据 。
知识点清单
- 路径占位符方式传递必选参数
- 查询参数(可选参数)的写法
- backStackEntry.arguments 获取传入参数
- SavedStateHandle 实现页面返回结果(A打开B,B返回数据回A)
- 禁止通过全局变量回传页面结果的弊端
完整示例源码
importandroidx.compose.foundation.layout.Arrangementimportandroidx.compose.foundation.layout.Columnimportandroidx.compose.foundation.layout.fillMaxSizeimportandroidx.compose.foundation.layout.paddingimportandroidx.compose.material3.Buttonimportandroidx.compose.material3.Scaffoldimportandroidx.compose.material3.Textimportandroidx.compose.material3.TopAppBarimportandroidx.compose.runtime.Composableimportandroidx.compose.runtime.getValueimportandroidx.compose.runtime.mutableStateOfimportandroidx.compose.runtime.rememberimportandroidx.compose.runtime.setValueimportandroidx.compose.ui.Alignmentimportandroidx.compose.ui.Modifierimportandroidx.navigation.NavHostControllerimportandroidx.navigation.compose.NavHostimportandroidx.navigation.compose.composableimportandroidx.navigation.compose.rememberNavControllerobjectRoute{constvalHOME="home"constvalEDIT="edit/{userName}"}@ComposablefunNavArgMainPage(){valnavController=rememberNavController()Scaffold{innerPadding->NavHost(navController=navController,startDestination=Route.HOME,modifier=Modifier.padding(innerPadding)){composable(Route.HOME){backStackEntry->HomeArgScreen(navController,backStackEntry.savedStateHandle)}composable(Route.EDIT){backStackEntry->//接收正向传递过来的参数valuserName=backStackEntry.arguments?.getString("userName")?:""EditArgScreen(navController,userName,backStackEntry.savedStateHandle)}}}}/** * 首页:打开编辑页;接收编辑页返回回来的结果 */@ComposablefunHomeArgScreen(navController:NavHostController,savedStateHandle:androidx.navigation.SavedStateHandle){//监听返回的数据,key为"result_name"varreceiveResultbyremember{mutableStateOf("尚未收到返回结果")}//读取savedStateHandle里面保存的返回值valreturnData=savedStateHandle.get<String>("result_name")if(returnData!=null){receiveResult=returnData}Column(modifier=Modifier.fillMaxSize(),verticalArrangement=Arrangement.Center,horizontalAlignment=Alignment.CenterHorizontally){Text(text="🏠首页,收到返回:$receiveResult")Button(onClick={//正向跳转,带上路径参数navController.navigate("edit/张三")}){Text("打开编辑页面(携带参数:张三)")}}}/** * 编辑页面:读取传入userName;修改之后把结果写进savedStateHandle,再返回上一页 */@ComposablefunEditArgScreen(navController:NavHostController,userName:String,savedStateHandle:androidx.navigation.SavedStateHandle){Column(modifier=Modifier.fillMaxSize(),verticalArrangement=Arrangement.Center,horizontalAlignment=Alignment.CenterHorizontally){Text(text="📝编辑页面,传入名字:$userName")Button(onClick={//向下一个页面返回结果,写入savedStateHandlesavedStateHandle["result_name"]="修改之后‑李四"//出栈回到首页navController.popBackStack()}){Text("保存并返回首页,带回结果")}}}逐行解读
- 正向路径传参(必选占位参数 {userName} )
路由字符串: “edit/{userName}” ; {userName} 是占位;
跳转的时候: navController.navigate(“edit/张三”) ;
在该页面的 backStackEntry.arguments?.getString(“userName”) 取出值。
必选参数必须填写;不写会直接崩溃。
- 可选查询参数(?key=value)简单说明
路由定义: “edit?name={name}” ;name可以不传;属于可选参数。
适合非必须的数据;日常开发必选占位符使用更多。 - 页面返回结果(B页面回传数据给A页面)核心‑SavedStateHandle
❗不要使用全局变量、单例来做页面返回结果;页面重建之后数据容易错乱。
每一个回退栈条目 backStackEntry 自带 savedStateHandle ,是轻量键值存储。
- B页面(子页面): savedStateHandle[“result_name”] = “修改之后‑李四” 写入返回数据;然后执行 popBackStack() 出栈。
- A页面(上一级页面):从自己的savedStateHandle读取 get(“result_name”) 拿到返回结果。
原理:数据保存在回退栈的条目上;页面出栈销毁,但是上一级的栈条目还保留,可以读到结果 。
- 参数传递的开发原则(非常重要)
导航路由上只传递基础类型:Int、Long、String、Boolean;禁止直接传递data class实体对象。
如果需要传递一条业务数据,只传递id;目标页面拿到id之后从仓库/ViewModel读取完整实体对象。
容易踩坑
- ❌ 把复杂实体对象直接通过路由传递;页面重建时对象会丢失,还可能引发序列化异常。
- ❌ 使用全局变量接收页面返回结果;旋转屏幕、进程重启之后逻辑直接出错。
- ❌ 返回结果写入B页面自己的savedStateHandle之后,忘记popBackStack();页面不会回去,首页收不到结果。
- ✅ 返回结果的key字符串首页与子页面必须完全一致;建议提取常量保存key。
多元化习题(练习)
习题1【基础‑改错题】
题目:下面路由跳转希望传递id(数字),找出错误。
composable("detail/{user}"){backStackEntry->valuser:User=backStackEntry.arguments?.getParcelable<User>("user")!!}答案:
错误:试图直接传递User实体对象;导航不适合传递实体。
修正:路由改为 “detail/{userId}” ,传递userId数字id;目标页面拿到userId,再从仓库查询完整User数据。
composable("detail/{userId}"){backStackEntry->valuserId=backStackEntry.arguments?.getLong("userId")}解读:
导航参数尽量只传id;复杂业务对象从数据源加载,这是官方推荐写法。
习题2【基础‑代码修改题】
题目:提取返回结果的key为常量 KEY_EDIT_RESULT = “edit_result” ;写出常量定义,以及子页面写入返回值的片段。
答案:
constvalKEY_EDIT_RESULT="edit_result"//子页面写入返回结果savedStateHandle[KEY_EDIT_RESULT]="新的名字‑王小明"navController.popBackStack()解读:
把key抽取常量;防止首页与子页面手写字符串不一致,导致接收不到返回数据。
习题3【进阶‑补充代码题】
题目:首页打开筛选页面;筛选页面选择分类之后,把分类id(Int=2)返回首页;写出筛选页面内部保存返回结果并且回退的代码片段。
答案:
savedStateHandle["filter_id"]=2navController.popBackStack()解读:
SavedStateHandle可以保存Int、String等基础类型;保存完成之后调用popBackStack回到上一页;首页读取同一个key拿到整型返回值。
习题4【综合‑逻辑应用题】
题目:A页面跳转B页面;B做完业务之后返回数据回A;为什么不推荐用全局变量接收B返回的数据?
答案:
当屏幕发生旋转,Activity重建,全局变量有可能丢失;同时页面多次打开时全局变量的值会被上一次结果残留污染;SavedStateHandle绑定导航回退栈生命周期;更可靠。
解读:
页面返回结果优先使用Navigation自带SavedStateHandle方案,而不是外部变量。
习题5【概念简答题】
题目:简述正向传参和页面返回结果(SavedStateHandle回传)的区别。
答案:
正向传参:A‑>B;跳转的时候拼接在路由路径上,数据随着跳转发送给下一个页面;
SavedStateHandle页面返回结果:B‑>A;B页面把结果写入savedStateHandle,然后出栈;A页面读取该key得到返回数据;是页面回退的时候反向的数据传递。
解读:
正向是跳转时携带;反向是页面退出时向上回传;这是页面之间两种数据通信方式。
本节课知识点总结
- 正向页面传参:路由 {paramName} 占位;跳转拼接参数;通过 backStackEntry.arguments 读取。
- 可选查询参数使用 ?key={val} ;日常开发必选占位符用得更多。
- 页面返回结果:利用SavedStateHandle;子页面写入键值,popBackStack回退;上一页读取该key的值。
- 禁止传递复杂实体对象;导航仅传递id等基础类型,业务实体根据id重新加载。
- 不要依靠全局变量做页面返回结果;页面重建容易带来数据错乱bug。
下一课预告:进阶第3课:ViewModel基础,页面状态管理与生命周期