Skip to content

路由配置

app/src/main/java/com/joker/kit/navigation/routes 负责声明所有页面/弹窗的“类型安全路由”。每个路由都是可序列化的数据类(或 data object),Compose Navigation 可以直接通过 composable<Route>() 推断路径与参数。Feature 模块在 feature/*/navigation 中注册路由,最后由 AppNavHost 汇总。

路由定义约定

  • 文件划分:按业务域拆分,例如 MainRoutesAuthRoutesUserRoutes。需要新增商品模块时,可创建 GoodsRoutes
  • 注解:统一使用 @Serializable,确保 Compose Navigation 在编译期生成路径解析器。
  • 参数:尽量使用显式字段(goodsId: Longtab: String 等),避免字符串拼接。例如:
kotlin
object UserRoutes {
    @Serializable
    data object Info
}

即便是无参页面也声明为 data object,保证写法一致。

在 NavGraph 中注册

Feature 模块通过 NavGraphBuilder 扩展函数注册页面。示例如 feature/user/navigation/UserNavigation.kt

kotlin
@OptIn(ExperimentalSharedTransitionApi::class)
fun NavGraphBuilder.userInfoScreen(sharedTransitionScope: SharedTransitionScope) {
    composable<UserRoutes.Info> {
        UserInfoRoute()
    }
}

Compose 1.7+ 支持 composable<Route>() 写法,无需再手写 route = "xxx" 字符串,也不需要 navArgument

各模块再提供一个聚合函数(如 mainGraph, userGraph),用于在 AppNavHost 中一次性挂载。

路由参数读取

Compose Navigation 已提供 toRoute<T>() 扩展,能够自动解析 @Serializable 路由对象。常见用法:

  • 在 Composable 中读取
    kotlin
    composable<GoodsRoutes.Detail> { backStackEntry ->
        val route = backStackEntry.toRoute<GoodsRoutes.Detail>()
        GoodsDetailRoute(goodsId = route.goodsId)
    }
  • 在 ViewModel 中读取
    基类 BaseNetWorkViewModel 会把 SavedStateHandle 注入子类,只需:
    kotlin
    @HiltViewModel
    class AddressDetailViewModel @Inject constructor(
        navigator: AppNavigator,
        userState: UserState,
        savedStateHandle: SavedStateHandle
    ) : BaseNetWorkViewModel<AddressDetail>(navigator, userState, savedStateHandle) {
    
        private val route = savedStateHandle.toRoute<UserRoutes.AddressDetail>()
        private val addressId = route.addressId
        // ...
    }

toRoute<T>() 会将字符串路由反序列化为 T,无需手写 SavedStateHandle["id"]

扩展示例(以“商品”模块为例)

假设现在需要新增一个商品模块,可参考以下流程:

  1. 定义路由:在 navigation/routes/GoodsRoutes.kt 中集中声明
    kotlin
    object GoodsRoutes {
        @Serializable data class Detail(val goodsId: Long)
        @Serializable data object Search
        @Serializable data object Comment
    }
  2. 声明页面:在 feature/goods/navigation/GoodsDetailNavigation.kt 等文件中注册
    kotlin
    fun NavGraphBuilder.goodsDetailScreen() {
        composable<GoodsRoutes.Detail> { GoodsDetailRoute() }
    }
    搜索、评论等页面也各自提供 NavGraphBuilder 扩展。
  3. 组合 Graph:创建 GoodsGraph.kt,统一调用 goodsDetailScreen()goodsSearchScreen() 等,并暴露 fun NavGraphBuilder.goodsGraph(...)。一个简化示例如下:
    kotlin
    fun NavGraphBuilder.goodsGraph(navController: NavHostController) {
        goodsDetailScreen()
        goodsSearchScreen()
        goodsCommentScreen()
        goodsCategoryScreen()
    }
    这层只负责拼装当前功能模块的所有页面,并提供给 AppNavHost
  4. 挂载到 AppNavHost:在 AppNavHostNavHost { ... } 中新增一行
    kotlin
    goodsGraph(navController, this@SharedTransitionLayout)
    这样 AppNavHost 只需按模块注册 Graph,像现有的 mainGraphauthGraphuserGraph 一样,大幅减少多人协作时对 AppNavHost 的频繁改动。

通过这种方式,每个业务模块都拥有独立的路由文件与导航函数,避免导航逻辑散落各处。

建议

  • 新建路由时务必同时更新:routes/*feature/*/navigationAppNavHost。三者缺一不可。
  • 若页面需要从 SavedStateHandle 读取参数,可使用 Compose Navigation 提供的 backStackEntry.toRoute<Route>() 扩展(Compose 1.7+ 自带),无需自己解析字符串。
  • Graph 函数只负责注册 composable,不要在里面直接处理导航跳转或状态逻辑,保持纯粹可测试。
  • 若已有存量模块,可先比照其 Routes + Navigation + Graph 结构,再按上述步骤迁移,保证层级一致。