使用popBackStack报错:NavController回退栈无对应路由问题求助
问题场景
需求:用户点击返回按钮时弹出AlertDialog确认是否离开当前页面,点击弹窗确认按钮后触发如下错误:
IllegalArgumentException: No destination with route CreatingAlbumGraph is on the NavController's back stack. The current destination is Destination(0x9993fa65) GalleryDestination
日志现象:进入AlbumSetupRoute和点击弹窗离开按钮时,均会打印$$ albumSetup remember parent entry,推测执行popBackStack()后,NavController尝试再次导航到AlbumSetupScreen。
相关代码片段
AlbumSetup.kt
@Serializable object AlbumSetupDestination @Composable fun AlbumSetupRoute( navController: NavHostController ) { val parentEntry = remember(navController.currentBackStackEntry) { Log.d(TAG, "$$ albumSetup remember parent entry") navController.getBackStackEntry(CreatingAlbumGraph) } val albumCreationViewModel: AlbumCreationViewModel = hiltViewModel(parentEntry) AlbumSetupScreen( onLeaveAlertClicked = { navController.popBackStack() }) } @Composable fun AlbumSetupScreen(onLeaveAlertClicked: () -> Unit) { Scaffold {} BackHandlingDialog( onLeaveClicked = onLeaveAlertClicked ) }
SetupNavGraph.kt
@Serializable object CreatingAlbumGraph @Composable fun SetupNavGraph() { NavHost( navController = navController, startDestination = SplashScreenDestination) { composable<SplashScreenDestination> { SplashScreen(navController) } composable<GalleryDestination>{ GalleryRoute(navController) } creatingAlbumGraph(navController) } } private fun NavGraphBuilder.creatingAlbumGraph(navController: NavHostController) { navigation<CreatingAlbumGraph>(startDestination = AlbumSetupDestination) { composable<AlbumSetupDestination> { AlbumSetupRoute(navController) } composable<FirstPhotoDestination> { FirstPhotoRoute(navController) } } }
问题原因
核心问题出在remember(navController.currentBackStackEntry)的依赖逻辑上:
- 执行
popBackStack()从AlbumSetupDestination回到GalleryDestination后,navController.currentBackStackEntry发生变化,触发remember块重新执行。 - 此时CreatingAlbumGraph已不在回退栈中,调用
navController.getBackStackEntry(CreatingAlbumGraph)直接抛出异常。 remember块的重复执行会引发Composable重组,尝试关联已不存在的导航图,进一步放大错误。
解决方法
方法一:正确获取父导航图的BackStackEntry
避免依赖currentBackStackEntry,直接从当前Composable的回退栈entry中获取父导航图的entry:
修改AlbumSetupRoute:
@Composable fun AlbumSetupRoute( navController: NavHostController, backStackEntry: NavBackStackEntry ) { val parentEntry = remember(backStackEntry) { navController.getBackStackEntry(CreatingAlbumGraph) } val albumCreationViewModel: AlbumCreationViewModel = hiltViewModel(parentEntry) AlbumSetupScreen( onLeaveAlertClicked = { navController.popBackStack(CreatingAlbumGraph, inclusive = false) }) }
同时在导航图中传递当前回退栈entry:
composable<AlbumSetupDestination> { backStackEntry -> AlbumSetupRoute(navController, backStackEntry) }
方法二:精准控制回退栈弹出逻辑
明确指定弹出目标为CreatingAlbumGraph,确保一次性退出整个嵌套导航图,避免后续重组错误:
修改onLeaveAlertClicked的实现:
AlbumSetupScreen( onLeaveAlertClicked = { // 弹出到CreatingAlbumGraph之前的页面,inclusive=false表示保留CreatingAlbumGraph的上一级页面 navController.popBackStack(CreatingAlbumGraph, inclusive = false) })
方法三:添加空值判断避免异常(兜底方案)
在获取父entry前先检查导航图是否存在于回退栈中:
val parentEntry = remember(navController.currentBackStackEntry) { Log.d(TAG, "$$ albumSetup remember parent entry") navController.backStack.firstOrNull { it.destination.route == CreatingAlbumGraph::class.simpleName } } val albumCreationViewModel: AlbumCreationViewModel? = parentEntry?.let { hiltViewModel(it) }
注:此方法可能导致ViewModel为空,仅作为兜底,优先推荐前两种方案。
内容的提问来源于stack exchange,提问作者koma222

