跨模块导航
连接各项功能而不产生耦合
跨模块导航 是 CoddyKit 上的免费 Android Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Android Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Android Academy 课程共包含 4 节课。
导航挑战
当功能位于不同模块中时,一个新问题出现了::feature:home 如何在不依赖 :feature:profile 的情况下打开其中的屏幕?如果功能模块直接相互导入,就会产生耦合,并可能形成循环依赖。
在本课中,您将通过导航连接各个功能,同时保持它们彼此独立。
NavHost 位于何处
唯一的 NavHost 位于:app 模块中,这是唯一允许了解所有功能的地方。每个功能提供自己的目标位置,:app 再将它们组装到同一个依赖图中。
// app/AppNavHost.kt
@Composable
fun AppNavHost(navController: NavHostController = rememberNavController()) {
NavHost(navController, startDestination = HomeRoute) {
homeScreen(onProfileClick = { navController.navigate(ProfileRoute) })
profileScreen(onBack = { navController.popBackStack() })
}
}类型安全的路由
现代 Navigation Compose 支持类型安全的路由:路由是 @Serializable 对象或数据类,而不是特殊字符串。每个功能都在自己的模块中定义路由类型,因此由该功能负责自己的导航契约。
// feature/profile/ProfileRoute.kt
import kotlinx.serialization.Serializable
@Serializable
data class ProfileRoute(val userId: String)功能模块公开 NavGraphBuilder 扩展
关键技巧是:每个功能公开一个NavGraphBuilder 扩展函数,用于注册其目标位置。功能模块负责自己的屏幕;:app 只需调用此函数。功能模块永远不会引用其他功能。
// feature/profile/ProfileNavigation.kt
fun NavGraphBuilder.profileScreen(onBack: () -> Unit) {
composable<ProfileRoute> { backStackEntry ->
val route: ProfileRoute = backStackEntry.toRoute()
ProfileScreen(userId = route.userId, onBack = onBack)
}
}使用导航回调实现解耦
功能模块绝不能直接调用 navigate(SomeOtherFeatureRoute),因为这样就必须依赖另一个功能。相反,功能模块应公开类似 onProfileClick 的lambda 回调。由 :app 模块决定它们实际要前往哪里。
// feature/home/HomeNavigation.kt
fun NavGraphBuilder.homeScreen(onProfileClick: (String) -> Unit) {
composable<HomeRoute> {
HomeScreen(onUserClick = { userId -> onProfileClick(userId) })
}
}
// :home does NOT know ProfileRoute exists.:app 模块负责连接一切
只有 :app 模块了解所有路由,并将回调连接到实际导航。这是唯一可以接受耦合的地方——它的全部职责就是组装。
// app/AppNavHost.kt
NavHost(navController, startDestination = HomeRoute) {
homeScreen(
onProfileClick = { userId ->
navController.navigate(ProfileRoute(userId)) // app knows both
}
)
profileScreen(onBack = { navController.popBackStack() })
}安全地传递参数
由于路由是 @Serializable 数据类,参数会在编译时进行类型检查。您可以在目标位置内部使用 toRoute() 读取参数。不再需要解析字符串,也不会再因运行时调用 arguments?.getString(...) 而崩溃。
// Reading arguments inside the destination
composable<ProfileRoute> { entry ->
val args: ProfileRoute = entry.toRoute()
ProfileScreen(userId = args.userId)
}
// Or directly in a ViewModel via SavedStateHandle
val route: ProfileRoute = savedStateHandle.toRoute()通过 api 模块共享路由类型
有时,一个功能确实需要导航到另一个功能,并且需要使用其路由类型。与其依赖整个功能,不如在一个很小的:feature:profile:api模块中仅公开路由,其中只包含 @Serializable 路由。体积较大的 :impl 模块仍保持私有。
// feature/profile/api -> only the route type
@Serializable
data class ProfileRoute(val userId: String)
// feature/home/build.gradle.kts
// home may depend on the lightweight api to build the route,
// but never on :feature:profile:impl
implementation(project(":feature:profile:api"))每个功能使用嵌套导航
包含多个屏幕的功能可以使用 navigation<T> 公开整个嵌套图。功能模块负责自己的内部流程;:app 只需在一个入口点挂载该依赖图。
// feature/onboarding/OnboardingNavigation.kt
fun NavGraphBuilder.onboardingGraph(onFinished: () -> Unit) {
navigation<OnboardingGraph>(startDestination = WelcomeRoute) {
composable<WelcomeRoute> { WelcomeScreen() }
composable<PermissionsRoute> { PermissionsScreen(onDone = onFinished) }
}
}跨模块深层链接
类型安全的路由同样支持深层链接。功能模块为自己的目标位置声明深层链接 URI 模式;:app NavHost 会将传入的链接解析到正确的功能屏幕,无需跨功能导入。
composable<ProfileRoute>(
deepLinks = listOf(
navDeepLink<ProfileRoute>(basePath = "https://myapp.com/profile")
)
) { entry ->
ProfileScreen(userId = entry.toRoute<ProfileRoute>().userId)
}解耦模式总结
综合起来,规则很简单:
- 每个功能负责自己的路由类型和 NavGraphBuilder 扩展。
- 功能之间通过lambda 回调传达意图,而不是直接进行导航。
- 只有 :app 了解所有功能,并将回调连接到实际路由。
- 如果必须共享路由,就通过一个很小的 :api 模块公开它。
这样可以让功能彼此独立、能够利用构建缓存,并且不会产生循环依赖。
快速检查
在一个多模块应用中,:feature:home 需要将用户带到 :feature:profile 中的某个屏幕。怎样才能以最整洁的方式保持功能模块彼此解耦?
回顾:跨模块导航
您已经学会在不产生耦合的情况下连接各个功能:
- 唯一的
NavHost位于精简的:app模块中。 - 每个功能负责一个
@Serializable路由和一个NavGraphBuilder扩展。 - 功能模块公开回调,而不是直接导航到其他功能。
- 只有在确实需要时,才通过较小的 :api 模块共享路由;深层链接和嵌套图也遵循相同模式。
至此,多模块应用架构的学习就完成了:现在您可以拆分、连接并导航一个可扩展的 Android 代码库。
用 AI 导师学习 Kotlin — 免费
在浏览器中编写并运行真实代码,获得全天候 AI 导师的即时帮助,并在网页或应用中继续学习。
- 课程
- 36
- 课程
- 152
常见问题解答
「跨模块导航」课时是免费的吗?
是的 — 「跨模块导航」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Android Academy 课程的其余内容,请升级到 CoddyKit PRO。 Android Academy 课程共包含 4 节课。
「跨模块导航」这节课中我会学到什么?
连接各项功能而不产生耦合 你通过在浏览器中直接运行的动手代码来练习 Android Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Android Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Android Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。
「跨模块导航」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Android Academy 课中编写并运行代码吗?
能。每节 Android Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。