You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jetpack Navigation双入口跳转同一公共页面时分类页崩溃问题

问题场景

现有三个页面:CategoryScreen、CompanyScreen、ProductsScreen,需要实现从CategoryScreen和CompanyScreen都能跳转到公共页ProductsScreen的导航逻辑。实际运行时从CompanyScreen发起跳转完全正常,从CategoryScreen发起跳转时应用直接崩溃,崩溃截图如下:
导航崩溃报错截图

问题相关实现代码

NavHost(
        navController = navController,
        startDestination = startDestination,
        modifier = modifier,
    ) {
        homeGraph(
            windowSizeClass = windowSizeClass,
            navigateToSubCategories = { navController.navigate(route = "${SubCategoryDestination.destination}/$this") },
            navigateToAllCategories = { navController.navigate(route = AllCategoriesDestination.destination) },
            nestedGraphs = {
                allCategoriesGraph(windowSizeClass = windowSizeClass,
                    onBackClick = { navController.popBackStack() },
                    navigateToProducts = {
                        navController.navigate(route = "${AllProductsDestination.destination}/$this/${ProductsType.CATEGORY.type}")
                    },
                    nestedGraphs = {
                        allProductsGraph(windowSizeClass = windowSizeClass,
                            onBackClick = {},
                            navigateToProductDetail = {
                                navController.navigate(ProductDetailDestination.route)
                            },
                            nestedGraphs = {
                                productDetailGraph()
                            })
                    })
            }
        )

        allCompaniesGraph(
            windowSizeClass = windowSizeClass,
            navigateToProducts = { navController.navigate( route = "${AllProductsDestination.destination}/$this/${ProductsType.COMPANY.type}") },
            nestedGraphs = {
                allProductsGraph(windowSizeClass = windowSizeClass,
                    onBackClick = {},
                    navigateToProductDetail = {
                        navController.navigate(ProductDetailDestination.route)
                    },
                    nestedGraphs = {
                        productDetailGraph()
                    })
            }
        )
    }

CompanyNavigation 文件代码

object AllCompaniesDestination : IFishoppingNavigationDestination {
    override val route: String
        get() = "all_companies_route"
    override val destination: String
        get() = "all_companies_destination"
}

@ExperimentalFoundationApi
@ExperimentalAnimationApi
@ExperimentalMaterial3Api
fun NavGraphBuilder.allCompaniesGraph(
    windowSizeClass: WindowSizeClass,
    navigateToProducts: String.() -> Unit,
    nestedGraphs: NavGraphBuilder.() -> Unit,
) {
    navigation(route = AllCompaniesDestination.route,
        startDestination = AllCompaniesDestination.destination) {
        composable(route = AllCompaniesDestination.destination) {
            CompaniesScreen(windowSizeClass = windowSizeClass,
                navigateToProducts = navigateToProducts)
        }
        nestedGraphs()
    }
}

CategoryNavigation 文件代码

object AllCategoriesDestination : IFishoppingNavigationDestination {
    override val route: String
        get() = "all_categories_route"
    override val destination: String
        get() = "all_categories_destination"
}

@ExperimentalAnimationApi
@ExperimentalLayoutApi
@ExperimentalMaterial3Api
fun NavGraphBuilder.allCategoriesGraph(
    windowSizeClass: WindowSizeClass,
    onBackClick: () -> Unit,
    navigateToProducts: String.() -> Unit,
    nestedGraphs: NavGraphBuilder.() -> Unit,
) {
    navigation(
        route = AllCategoriesDestination.route,
        startDestination = AllCategoriesDestination.destination
    ) {
        composable(route = AllCategoriesDestination.destination) {
            AllCategoriesScreen(windowSizeClass = windowSizeClass,
                onBackClick = {},
                navigateToProducts = { navigateToProducts() })
        }
        nestedGraphs()
    }
}

公共目标页 ProductNavigation 文件代码

object AllProductsDestination : IFishoppingNavigationDestination {
    override val route: String
        get() = "all_products_route"
    override val destination: String
        get() = "all_products_destination"
    const val allProductsArg = "allProductsParam"
    const val allProductsRequestTypeArg = "allProductsRequestTypeParam"
}

@ExperimentalLayoutApi
@ExperimentalMaterial3Api
fun NavGraphBuilder.allProductsGraph(
    windowSizeClass: WindowSizeClass,
    onBackClick: () -> Unit,
    navigateToProductDetail: String.() -> Unit,
    nestedGraphs: NavGraphBuilder.() -> Unit,
) {
    navigation(route = AllProductsDestination.route,
        startDestination = AllProductsDestination.destination)
    {
        composable(route = AllProductsDestination.destination
            .plus("/{${AllProductsDestination.allProductsArg}}")
            .plus("/{${AllProductsDestination.allProductsRequestTypeArg}}"),
            arguments = listOf(
                navArgument(AllProductsDestination.allProductsArg) {
                    type = NavType.StringType
                },
                navArgument(AllProductsDestination.allProductsRequestTypeArg) {
                    type = NavType.StringType
                }
            )) {
            ProductsScreen(windowSizeClass = windowSizeClass, onBackClick = onBackClick)
        }
        nestedGraphs()
    }
}
崩溃根因
  1. 公共导航图重复注册:你把allProductsGraph分别嵌套在allCategoriesGraph和allCompaniesGraph两个独立的业务子导航图内部,相当于同一个导航目标被重复定义在两个不同的父导航域下,导航控制器匹配路由时会出现规则冲突。
  2. 分类页跳转入口错误:跳AllCategories页面时你用的路由是AllCategoriesDestination.destination,但allCategoriesGraph的父导航入口路由是AllCategoriesDestination.route,直接跳destination相当于绕过嵌套导航的入口直接访问内部页面,导航控制器无法匹配到注册在这个子图内部的products路由规则,直接抛出找不到目的地的崩溃。
  3. 路由拼接不统一:跳转Products页面时你用AllProductsDestination.destination拼接参数,但注册Products路由时的父导航入口是AllProductsDestination.route,路径前缀不匹配也会导致路由找不到。
  4. 附带的小bug:AllCategoriesScreen里的onBackClick被硬编码为空函数,传入的返回回调被覆盖,后续返回功能也会异常。
修复方案
  1. 公共页面的导航图不要嵌套在任何业务子导航图里,把allProductsGraph移到NavHost的顶级注册位置,和homeGraph、allCompaniesGraph平级。
  2. 所有跳转嵌套导航图的逻辑,统一走导航图的route入口,不要直接跳内部的destination。
  3. 统一Products页面的路由常量,避免手动拼接路径出现前缀不匹配的问题。
  4. 修复参数透传逻辑,不要覆盖传入的回调函数。

修复后核心代码

修正NavHost注册逻辑

NavHost(
    navController = navController,
    startDestination = startDestination,
    modifier = modifier,
) {
    homeGraph(
        windowSizeClass = windowSizeClass,
        navigateToSubCategories = { navController.navigate(route = "${SubCategoryDestination.destination}/$this") },
        // 走分类导航图的route入口
        navigateToAllCategories = { navController.navigate(route = AllCategoriesDestination.route) },
        nestedGraphs = {
            allCategoriesGraph(windowSizeClass = windowSizeClass,
                onBackClick = { navController.popBackStack() },
                navigateToProducts = { categoryId ->
                    // 跳公共products页用统一的完整路由
                    navController.navigate(route = "${AllProductsDestination.fullRoute}/$categoryId/${ProductsType.CATEGORY.type}")
                }
            )
        }
    )

    allCompaniesGraph(
        windowSizeClass = windowSizeClass,
        navigateToProducts = { companyId ->
            // 跳公共products页用统一的完整路由
            navController.navigate( route = "${AllProductsDestination.fullRoute}/$companyId/${ProductsType.COMPANY.type}")
        }
    )

    // 公共products图作为顶级导航注册,不嵌套在业务子图内
    allProductsGraph(windowSizeClass = windowSizeClass,
        onBackClick = { navController.popBackStack() },
        navigateToProductDetail = {
            navController.navigate(ProductDetailDestination.route)
        },
        nestedGraphs = {
            productDetailGraph()
        }
    )
}

修正ProductNavigation路由配置

object AllProductsDestination : IFishoppingNavigationDestination {
    override val route: String
        get() = "all_products_route"
    override val destination: String
        get() = "all_products_destination"
    const val allProductsArg = "allProductsParam"
    const val allProductsRequestTypeArg = "allProductsRequestTypeParam"
    // 统一定义完整路由,避免拼接错误
    const val fullRoute = "$route/{$allProductsArg}/{$allProductsRequestTypeArg}"
}

@ExperimentalLayoutApi
@ExperimentalMaterial3Api
fun NavGraphBuilder.allProductsGraph(
    windowSizeClass: WindowSizeClass,
    onBackClick: () -> Unit,
    navigateToProductDetail: String.() -> Unit,
    nestedGraphs: NavGraphBuilder.() -> Unit,
) {
    navigation(
        route = AllProductsDestination.route,
        startDestination = AllProductsDestination.fullRoute
    )
    {
        composable(
            route = AllProductsDestination.fullRoute,
            arguments = listOf(
                navArgument(AllProductsDestination.allProductsArg) {
                    type = NavType.StringType
                },
                navArgument(AllProductsDestination.allProductsRequestTypeArg) {
                    type = NavType.StringType
                }
            )
        ) { backStackEntry ->
            // 从栈中取出导航参数传给页面
            val targetId = backStackEntry.arguments?.getString(AllProductsDestination.allProductsArg) ?: ""
            val requestType = backStackEntry.arguments?.getString(AllProductsDestination.allProductsRequestTypeArg) ?: ""
            ProductsScreen(
                windowSizeClass = windowSizeClass, 
                onBackClick = onBackClick,
                targetId = targetId,
                requestType = requestType
            )
        }
        nestedGraphs()
    }
}

修正CategoryNavigation的回调透传

@ExperimentalAnimationApi
@ExperimentalLayoutApi
@ExperimentalMaterial3Api
fun NavGraphBuilder.allCategoriesGraph(
    windowSizeClass: WindowSizeClass,
    onBackClick: () -> Unit,
    navigateToProducts: String.() -> Unit,
    nestedGraphs: NavGraphBuilder.() -> Unit = {},
) {
    navigation(
        route = AllCategoriesDestination.route,
        startDestination = AllCategoriesDestination.destination
    ) {
        composable(route = AllCategoriesDestination.destination) {
            AllCategoriesScreen(
                windowSizeClass = windowSizeClass,
                onBackClick = onBackClick, // 不要硬编码空函数,透传传入的返回回调
                navigateToProducts = { categoryId -> navigateToProducts(categoryId) }
            )
        }
        nestedGraphs()
    }
}

内容的提问来源于stack exchange,提问作者Saeed Noshadi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 19:45:59