多模块Compose项目中Feature与Login模块导航及循环依赖解决
多模块Compose跨模块导航问题解决方案
一、解决循环依赖:抽离导航契约到Common模块
核心思路是把跨模块导航的核心定义(路由、导航意图)放到所有模块都依赖的common模块,避免业务模块间直接引用,从根源消除循环依赖。
1. 在Common模块定义导航契约
创建统一的路由和导航事件类,让所有模块基于这个契约做导航:
// common模块/NavigationContract.kt // 定义所有页面路由 sealed class ScreenRoute(val route: String) { object Login : ScreenRoute("login") object Feature1 : ScreenRoute("feature1") object Feature2 : ScreenRoute("feature2") } // 定义导航事件,用于传递跨模块跳转意图 sealed class NavigationEvent { object NavigateToLogin : NavigationEvent() data class NavigateToFeature(val route: ScreenRoute) : NavigationEvent() }
2. 集中导航逻辑到App模块
App模块作为唯一知晓所有业务模块的入口,负责托管NavHost并注册所有页面,同时处理导航事件:
// app模块/MainActivity.kt class MainActivity : ComponentActivity() { private val navigationEvent = mutableStateOf<NavigationEvent?>(null) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MyAppTheme { val navController = rememberNavController() // 监听导航事件并执行跳转 LaunchedEffect(navigationEvent.value) { navigationEvent.value?.let { event -> when (event) { NavigationEvent.NavigateToLogin -> navController.navigate(ScreenRoute.Login.route) is NavigationEvent.NavigateToFeature -> navController.navigate(event.route.route) } navigationEvent.value = null } } // 注册所有模块的页面 NavHost(navController = navController, startDestination = ScreenRoute.Login.route) { composable(ScreenRoute.Login.route) { LoginScreen(onNavigateToFeature = { route -> navigationEvent.value = NavigationEvent.NavigateToFeature(route) }) } composable(ScreenRoute.Feature1.route) { Feature1Screen(onLogout = { navigationEvent.value = NavigationEvent.NavigateToLogin }) } composable(ScreenRoute.Feature2.route) { Feature2Screen(onLogout = { navigationEvent.value = NavigationEvent.NavigateToLogin }) } } } } } }
3. 业务模块仅依赖Common契约,无需引用其他业务模块
以Login模块为例,只需要接收导航回调,不需要知道具体的Feature模块实现:
// login模块/LoginScreen.kt @Composable fun LoginScreen(onNavigateToFeature: (ScreenRoute) -> Unit) { Column { Button(onClick = { onNavigateToFeature(ScreenRoute.Feature1) }) { Text("进入Feature1") } Button(onClick = { onNavigateToFeature(ScreenRoute.Feature2) }) { Text("进入Feature2") } } }
Feature模块同理,只需要触发登出的导航事件,不需要直接依赖Login模块:
// feature1模块/Feature1Screen.kt @Composable fun Feature1Screen(onLogout: () -> Unit) { Button(onClick = onLogout) { Text("登出到登录页") } }
这样所有业务模块(login、feature1、feature2)都只依赖common模块,App模块统一管理导航逻辑,完全避免循环依赖。
二、关于是否必须使用依赖注入(DI)
不是必须的。
如果项目规模较小、导航逻辑简单,用上述回调+状态传递的方式就能满足需求。但当模块增多、导航逻辑变得复杂时,DI可以帮助你更优雅地管理导航控制器、导航事件分发(比如配合ViewModel注入,把导航逻辑从Activity中抽离),让代码结构更清晰、更易维护。
总结:初期或小项目可以不用DI;项目规模扩大后,引入DI会提升代码的可扩展性。
内容的提问来源于stack exchange,提问作者blackHawk
相关产品推荐
相关产品推荐

