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

多模块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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 05:25:15