Compose中使用DeepLinks后导航返回栈异常无法正常回退
问题根因
返回栈堆积重复实例是三个逻辑问题共同导致的:
- 中间页
LaunchedEffect的key设置错误。你用statementsByYear == null这个布尔值作为触发key,第一次进入页面时数据为null,协程内的导航逻辑根本不会执行;等数据加载完成key变为false,协程重启执行导航后,只要后续重组导致这个布尔值波动,就会反复触发导航,重复压入MonthlyBillings实例。 - 从中间页跳转到目标页时没有配置栈清理规则,中间过渡页会一直残留在返回栈中。用户按返回键回到中间页后,会再次触发导航逻辑,循环叠加页面实例。
- 导航没有开启单实例模式,重复触发导航时不会复用栈内已有的目标页,只会持续创建新实例。
修改方案
1. 修复中间页的LaunchedEffect逻辑,增加防重复执行标记
不要用布尔值作为effect的key,直接以可观察的数据对象作为key,同时加执行标记避免重组重复触发导航:
@Composable fun IntermediateMonthlyBillings( onDataAcquired: (AllStatementsByYear) -> Unit, myEwayLoggedInViewModel: MyEwayLoggedInViewModel = get() ) { val statementsByYear by myEwayLoggedInViewModel.statementsByYear.observeAsState(null) var hasTriggeredNavigation by remember { mutableStateOf(false) } if (statementsByYear == null) { GenericLoader(type = MyLoaderType.LIGHT_BACKGROUND) } LaunchedEffect(statementsByYear) { val data = statementsByYear if (data != null && !hasTriggeredNavigation) { hasTriggeredNavigation = true onDataAcquired(data) } } }
2. 配置导航栈操作规则,跳转时移除中间页
从中间页导航到MonthlyBillings时,添加popUpTo规则把中间页从栈中完全移除,同时开启单实例模式避免重复创建目标页:
composable(Screens.INTERMEDIATE_MONTHLY_BILLINGS.navRoute, deepLinks = listOf( navDeepLink { uriPattern = "eway://traversals_monthly_bill_details" } )) { IntermediateMonthlyBillings({ statements -> val lastIndex = statements.lastIndex val gson = GsonBuilder().create() val statementsByYearJson = gson.toJson(statements) navController.navigate("${Screens.MONTHLY_BILLINGS}/$statementsByYearJson/$lastIndex") { popUpTo(Screens.INTERMEDIATE_MONTHLY_BILLINGS.navRoute) { inclusive = true // 弹出时包含中间页自身 } launchSingleTop = true // 栈内已有目标页时直接复用,不新建实例 } }) }
给MonthlyBillings的composable配置也加上单实例支持:
composable( route = "${Screens.MONTHLY_BILLINGS.navRoute}/{statementsByYear}/{initialIndex}", arguments = listOf( navArgument("statementsByYear") {}, navArgument("initialIndex") { type = NavType.IntType } ) ) { val gson = GsonBuilder().create() val statementsByYear = gson.fromJson( it.arguments?.getString("statementsByYear"), AllStatementsByYear::class.java ) MonthlyBillings(statementsByYear, it.arguments?.getInt("initialIndex")!!) updateCurrentScreen(Screens.MONTHLY_BILLINGS) }
3. 修正PendingIntent的flag适配系统要求
安卓12及以上版本要求必须显式声明PendingIntent的可变性,补充FLAG_IMMUTABLE标记即可,TaskStackBuilder自带的addNextIntentWithParentStack会自动按照导航图的层级关系构建标准返回栈,也就是你期望的ScreenA、ScreenC、ScreenF、MonthlyBillings结构,不需要额外调整:
TaskStackBuilder.create(this).run { addNextIntentWithParentStack( Intent( Intent.ACTION_VIEW, "eway://traversals_monthly_bill_details".toUri() ) ) getPendingIntent( 2345, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) }
推荐优化(可选)
直接删掉IntermediateMonthlyBillings这个中间页,把deeplink直接绑定到MonthlyBillings的composable上,在MonthlyBillings页面内部监听ViewModel的数据状态:数据未加载时直接显示加载动画,加载完成后渲染页面内容。这种方式完全避免了中间页带来的栈管理问题,代码链路更短更稳定。
内容的提问来源于stack exchange,提问作者Stelios Papamichail
相关产品推荐
相关产品推荐

