Jetpack Compose:如何为多风味应用的NavHost定义专属导航?
优化多风味Android应用的导航模块关联方案
针对你当前多风味应用中导航代码冗余、重复实现的问题,提供两种更优的实现思路,适配当前场景与未来扩展需求:
方案一:Gradle共享源码集+模块内导航封装
核心思路是将需要关联:feature:cars的风味归为同一组,共享导航扩展代码,避免重复实现;同时通过Gradle控制模块依赖范围,仅给目标风味引入:feature:cars。
步骤1:配置Gradle变体与共享源码集
在app模块的build.gradle.kts中,给风味分组并创建共享源码集:
flavorDimensions += "appFlavor" productFlavors { create("flavorA") { dimension = "appFlavor" buildConfigField("boolean", "ENABLE_CARS", "false") } create("flavorB") { dimension = "appFlavor" buildConfigField("boolean", "ENABLE_CARS", "true") } create("flavorC") { dimension = "appFlavor" buildConfigField("boolean", "ENABLE_CARS", "true") } create("flavorD") { dimension = "appFlavor" buildConfigField("boolean", "ENABLE_CARS", "true") } } // 创建共享源码集,供启用Cars模块的风味复用 sourceSets { val carsEnabled by creating { java.srcDir("src/carsEnabled/java") res.srcDir("src/carsEnabled/res") } // 将flavorB/C/D关联到共享源码集 listOf("flavorB", "flavorC", "flavorD").forEach { flavorName -> getByName(flavorName).apply { java.srcDir(carsEnabled.java.srcDirs) res.srcDir(carsEnabled.res.srcDirs) } } } // 仅给启用Cars的风味添加模块依赖 dependencies { listOf("flavorB", "flavorC", "flavorD").forEach { flavorName -> "${flavorName}Implementation"(project(":feature:cars")) } }
步骤2:在:feature:cars模块封装导航扩展
在:feature:cars模块中创建导航扩展函数,封装所有Cars相关的导航配置:
// :feature:cars模块下的NavigationExtensions.kt fun NavGraphBuilder.addCarsNavigation(navController: NavHostController) { composable(Destination.Cars.route) { CarsScreen() } composable(Destination.CreateCar.route) { CreateCarScreen() } // 其他Cars相关导航节点 }
步骤3:实现风味专属的导航扩展
- 在共享源码集
src/carsEnabled/java/...中,实现addSpecificNavigation:
fun NavGraphBuilder.addSpecificNavigation(navController: NavHostController) { addCarsNavigation(navController) // 直接调用模块封装的导航 }
- 在
src/flavorA/java/...中保留空实现:
fun NavGraphBuilder.addSpecificNavigation(navController: NavHostController) { // 空实现,无额外导航节点 }
步骤4:主工程NavHost保持原有调用
你的NavHost代码无需修改,依然调用addSpecificNavigation即可:
NavHost(navController, startDestination = startDestinationRoute) { addSpecificNavigation(navController) composable(Onboarding.route) { OnboardingScreen(appState = appState) } // 其他通用导航节点 }
方案二:导航提供者接口+依赖注入条件绑定
如果未来会新增大量功能模块与风味,推荐使用接口化+DI的方式,实现松耦合的导航注册,无需修改主工程核心代码。
步骤1:定义导航提供者接口
在主工程或公共模块中定义接口:
interface NavigationProvider { fun NavGraphBuilder.registerNavigation(navController: NavHostController) }
步骤2:功能模块实现导航提供者
在:feature:cars模块中实现接口,封装导航逻辑:
class CarsNavigationProvider : NavigationProvider { override fun NavGraphBuilder.registerNavigation(navController: NavHostController) { composable(Destination.Cars.route) { CarsScreen() } composable(Destination.CreateCar.route) { CreateCarScreen() } } }
步骤3:DI条件绑定导航提供者
以Hilt为例,在app模块的src/carsEnabled/java/...中创建绑定模块:
@Module @InstallIn(SingletonComponent::class) object CarsNavigationModule { @Provides @IntoSet fun provideCarsNavigationProvider(): NavigationProvider = CarsNavigationProvider() }
flavorA的源码集中不添加此绑定,自然不会注册该导航提供者。
步骤4:主工程注入并遍历注册
在NavHost所在的类中注入Set<NavigationProvider>,遍历调用注册:
@Inject lateinit var navigationProviders: Set<NavigationProvider> // 在NavHost构建逻辑中 NavHost(navController, startDestination = startDestinationRoute) { // 遍历所有注册的导航提供者,添加对应导航节点 navigationProviders.forEach { provider -> provider.registerNavigation(navController) } composable(Onboarding.route) { OnboardingScreen(appState = appState) } // 其他通用导航节点 }
方案对比
- 方案一:实现简单,无额外依赖,适合当前场景,未来新增风味只需调整Gradle配置即可。
- 方案二:扩展性强,模块间松耦合,适合未来大量功能模块与风味的迭代场景。
内容的提问来源于stack exchange,提问作者tasjapr
相关产品推荐
相关产品推荐

