모듈 간 탐색
결합도를 높이지 않고 기능을 연결합니다.
모듈 간 탐색은(는) CoddyKit의 무료 Android Academy 강의입니다. 이것은 4개 중 4번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 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과 같은 람다 콜백을 노출합니다. 실제로 어디로 이동할지는 :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 모듈로 경로 타입 공유하기
때로는 한 기능이 다른 기능으로 실제로 이동해야 하며 경로 타입도 필요합니다. 전체 기능에 의존하는 대신 @Serializable 경로만 포함하는 작은 :feature:profile:api 모듈에 경로를 노출하세요. 무거운 :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 확장을 소유합니다.
- 기능은 직접 탐색하지 않고 람다 콜백으로 의도를 전달합니다.
- :app만 모든 기능을 알고 콜백을 실제 경로에 연결합니다.
- 경로를 공유해야 한다면 작은 :api 모듈을 통해 노출합니다.
이렇게 하면 기능이 독립적으로 유지되고 빌드 캐시를 사용할 수 있으며 순환도 발생하지 않습니다.
빠른 확인
다중 모듈 앱에서 :feature:home이 :feature:profile의 화면으로 사용자를 이동해야 합니다. 기능 간 결합을 해제된 상태로 유지하는 가장 깔끔한 방법은 무엇일까요?
복습: 모듈 간 탐색
기능을 결합하지 않고 연결하는 방법을 배웠습니다.
- 하나의
NavHost는 얇은 :app 모듈에 있습니다. - 각 기능은
@Serializable경로와NavGraphBuilder확장을 소유합니다. - 기능은 다른 기능으로 직접 탐색하는 대신 콜백을 노출합니다.
- 정말 필요할 때만 작은 :api 모듈을 통해 경로를 공유하며, 딥 링크와 중첩 그래프에도 같은 패턴을 적용합니다.
이로써 다중 모듈 앱 아키텍처를 마칩니다. 이제 확장 가능한 Android 코드베이스를 나누고, 연결하고, 탐색할 수 있습니다.
AI 튜터와 함께 Kotlin을(를) 배우세요 — 무료
브라우저에서 실제 코드를 작성하고 실행하며, 24/7 AI 튜터로부터 즉각적인 도움을 받고, 웹이나 앱에서 중단한 부분부터 계속 학습하세요.
- 코스
- 36
- 레슨
- 152
자주 묻는 질문
“모듈 간 탐색” 강의는 무료인가요?
네 — “모듈 간 탐색” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Android Academy 강의 전체를 잠금 해제할 수 있습니다. Android Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
“모듈 간 탐색”에서 뭘 배우나요?
결합도를 높이지 않고 기능을 연결합니다. 브라우저에서 직접 실행하는 실습 코드로 Android Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
Android Academy을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 Android Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 4번째 강의입니다.
“모듈 간 탐색” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 Android Academy 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 Android Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.