Navigation Compose는 Jetpack Compose로 구축된 Android 애플리케이션 내에서 선언적 네비게이션을 위한 Jetpack 라이브러리입니다. FragmentManager 또는 Intent 기반 네비게이션 대신, Navigation Compose는 NavController와 NavHost를 통해 관리되는 단일 경로 그래프를 제공합니다. Google I/O(2025)에 따르면, Navigation Compose는 Compose 애플리케이션의 권장 네비게이션 방식이며, 신규 프로젝트의 70% 이상에서 사용됩니다. 이 라이브러리는 형식화된 인수 전달, 딥 링크, 전환 애니메이션 및 SavedStateHandle을 통한 ViewModel과의 통합을 지원합니다.
핵심 사항
Navigation Compose는 Compose 애플리케이션을 위한 네비게이션 프레임워크를 제공하는 Jetpack 제품군의 라이브러리입니다. 이 라이브러리는 View 시스템용 Navigation Component와 동일한 원칙을 기반으로 하지만, Compose의 선언적 특성에 맞게 조정되었습니다. FragmentTransaction 대신 composable 함수가 사용되며, 네비게이션 그래프는 Kotlin DSL을 통해 구축됩니다.
Navigation Compose와 기존 네비게이션의 주요 차이점은 FragmentManager가 필요 없다는 것입니다. 각 화면은 composable 함수이며, 경로가 일치하면 NavHost에서 렌더링됩니다. 백 스택은 Fragment가 아니라 경로, 인수 및 상태가 포함된 레코드를 저장합니다. 이는 아키텍처를 단순화하고 Fragment 기반 네비게이션의 일반적인 라이프사이클 충돌을 제거합니다.
Google(2025)에 따르면, Navigation Compose는 실험 단계에서 안정화 단계로 전환되었으며 버전 2.8.0부터 Jetpack의 일부입니다. 이 라이브러리는 Material3, 형식 안전 네비게이션(Kotlin Serialization을 통해), 중첩 그래프 및 모듈화를 지원합니다. 유일한 제한 사항은 수동 구성 없이 BottomNavigation의 다중 백 스택을 지원하지 않는다는 점이지만, Google이 이 문제를 해결하기 위해 작업 중입니다.
Navigation Compose의 아키텍처는 세 가지 엔터티를 중심으로 구성됩니다: NavController(스택 관리), NavHost(그래프 컨테이너), NavDestination(composable이 있는 개별 경로). 이들 간의 상호 작용은 선언적입니다. 개발자가 경로와 인수를 설명하면 라이브러리가 로드, 유지 및 복원 상태를 처리합니다.
NavController는 Navigation Compose의 중심 요소로, 네비게이션 스택을 관리합니다. rememberNavController()를 통해 생성되며 NavHost에 전달됩니다. NavController는 백 스택, 현재 진입점을 저장하고 지연된 작업(그래프 초기화 후 딥링크)을 지원합니다.
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "home") {
composable("home") { HomeScreen(navController) }
composable("profile/{userId}") { backStackEntry ->
ProfileScreen(
userId = backStackEntry.arguments?.getString("userId") ?: ""
)
}
}
}
NavController의 주요 메서드: navigate(route) — 경로로 이동, popBackStack() — 이전 화면으로 돌아가기, navigateAndClear(route) — 스택을 지우고 이동. NavOptions는 동작을 지정합니다: launchSingleTop은 중복을 방지, popUpTo는 지정된 경로까지 스택을 지움, restoreState는 이전 상태를 복원합니다.
깊게 중첩된 composable 함수에서 NavController에 액세스하려면 CompositionLocal을 통해 NavHostController를 사용하세요. LocalNavController는 NavHost 내의 ScopedNavController에서 제공됩니다. NavHost 외부(예: BottomNavigation)에서는 컨트롤러가 매개변수 또는 ViewModel을 통해 전달됩니다.
NavHost는 NavController를 경로 그래프에 연결하는 composable 컨테이너입니다. 각 경로는 composable(route, arguments, deepLinks)를 통해 선언되며, 여기서 route는 선택적 {param} 자리 표시자가 있는 경로 문자열입니다. 현재 경로가 일치하면 NavHost가 해당 composable 블록을 렌더링합니다.
경로 그래프는 계층적으로 구축됩니다. 모듈 내에서 경로를 그룹화하기 위해 navigation()을 통해 그래프를 중첩할 수 있습니다. 중첩된 그래프는 자체 startDestination을 가지며 공통 경로 접두사 아래 결합됩니다. 이를 통해 각 기능 모듈이 자체 하위 그래프를 등록하는 모듈식 아키텍처를 구성할 수 있습니다.
NavHost(
navController = navController,
startDestination = "main"
) {
composable("main") { MainScreen(navController) }
navigation(
route = "auth",
startDestination = "auth/login"
) {
composable("auth/login") { LoginScreen(navController) }
composable("auth/register") { RegisterScreen(navController) }
}
}
NavHost는 LocalBackDispatcher를 통해 시스템 뒤로 가기 버튼(백 프레스)을 자동으로 처리합니다. Material3 Scaffold에서는 기본적으로 NavController를 가져와 BottomNavigation이 올바르게 작동하도록 합니다. NavHost는 경로가 변경되면 composable을 다시 생성하지만, rememberSaveable을 통해 입력 필드와 스크롤의 상태를 유지합니다.
Navigation Compose는 경로 매개변수와 NavType을 통해 화면 간 형식화된 인수 전달을 지원합니다. 매개변수는 경로에서 {paramName}으로 지정되며 composable()의 arguments를 통해 유형이 표시됩니다. NavType은 String, Int, Long, Float, Boolean, Parcelable 및 Serializable을 지원합니다.
| 인수 유형 | NavType | 경로 예시 |
|---|---|---|
| String | NavType.StringType | "profile/{name}" |
| Int | NavType.IntType | "item/{id}" |
| Boolean | NavType.BoolType | "settings?enabled={flag}" |
| Parcelable | NavType.ParcelableType | "details/{item}" |
| Float | NavType.FloatType | "map?lat={lat}&lng={lng}" |
인수는 NavBackStackEntry에서 arguments?.getType(key)를 통해 추출됩니다. 필수 매개변수에는 defaultValue를 사용하고, 선택적 매개변수에는 nullable을 사용합니다. Parcelable 지원은 Kotlin Parcelize 또는 kotlinx.parcelize 라이브러리에서만 작동합니다. 복잡한 객체의 경우, 전체 객체를 직렬화하는 대신 ID를 전달하고 ViewModel을 통해 데이터를 로드하는 것이 좋습니다.
Navigation 2.8.0부터 Kotlin Serialization을 사용한 형식 안전 네비게이션이 도입되었습니다. 경로는 데이터 클래스로, 인수는 필드로 정의됩니다. 이는 문자열 경로를 형식화된 객체로 대체하고 경로 이름의 오류를 제거합니다. 마이그레이션에는 Kotlin Serialization 플러그인과 navigation-compose-typesafe 종속성이 필요합니다.
@Serializable
/* sealed class Route */
sealed class ProfileRoute(val route: String) {
data object Home : ProfileRoute("home")
data class Profile(val userId: String) : ProfileRoute("profile/{userId}")
}
딥 링크는 URL 또는 intent-filter를 통해 특정 애플리케이션 화면을 열 수 있는 네비게이션 메커니즘입니다. Navigation Compose에서 딥 링크는 composable()의 deepLinks 매개변수를 통해 구성되며 URI가 패턴과 일치하면 자동으로 처리됩니다.
딥 링크는 UriPattern 목록으로 지정됩니다: "https://example.com/profile/{userId}". URI 매개변수는 자동으로 경로 인수에 매핑됩니다. NavController는 애플리케이션 시작 시(intent를 통해)와 작동 중(암시적 딥 링크를 통해) 딥 링크를 처리합니다. 보류 중인 딥 링크를 처리하려면 그래프 초기화 후 NavController에서 handleDeepLink()를 사용하세요.
Google에 따르면 딥 링크는 다음에 권장됩니다: 푸시 알림(Firebase Dynamic Links), 이메일 확인, 콘텐츠 공유 및 웹 링크를 통한 교차 네비게이션. Android 12+의 경우, 딥 링크 권한을 확인하기 위해 Digital Asset Links가 사용됩니다. AndroidManifest.xml에는 대화 상자 없이 링크를 열기 위해 autoVerify="true"가 있는 intent-filter가 포함되어야 합니다.
composable(
route = "profile/{userId}",
arguments = listOf(navArgument("userId") { type = NavType.StringType }),
deepLinks = listOf(
navDeepLink { uriPattern = "https://example.com/profile/{userId}" }
)
) { backStackEntry ->
ProfileScreen(userId = backStackEntry.arguments?.getString("userId") ?: "")
}
Navigation Compose에서 딥 링크의 제한 사항: 라이브러리는 지연된 딥 링크를 지원하지 않습니다. 딥 링크는 NavHost가 그래프를 완전히 구축한 후에만 처리됩니다. 그래프 초기화 전에 딥 링크가 도착하면 intent?.data를 통해 지연시키고 LaunchedEffect에서 처리해야 합니다. Firebase Dynamic Links의 경우 Navigation Compose와 함께 Firebase Dynamic Links SDK를 사용하세요.
Navigation Compose는 composable()의 enterTransition, exitTransition, popEnterTransition 및 popExitTransition 매개변수를 통해 전환 애니메이션을 지원합니다. 애니메이션은 Compose Animation API(fadeIn, slideInHorizontally, expandIn 등)를 사용하여 구현됩니다. 기본적으로 애니메이션은 비활성화되어 있으며 화면이 즉시 전환됩니다.
일반적인 애니메이션 시나리오: 앞으로 네비게이션에는 slideInHorizontally(화면이 오른쪽에서 슬라이드 인), 돌아가기에는 slideOutHorizontally(화면이 오른쪽으로 슬라이드 아웃). BottomNavigation의 경우 슬라이드 없는 페이드 애니메이션이 더 일반적으로 사용됩니다. 애니메이션은 NavHost를 통해 설정되며 개별적으로 지정되지 않는 한 모든 composable에 적용됩니다.
NavHost(
navController = navController,
startDestination = "home",
enterTransition = { slideInHorizontally() + fadeIn() },
exitTransition = { slideOutHorizontally() + fadeOut() },
popEnterTransition = { fadeIn() },
popExitTransition = { slideOutHorizontally() + fadeOut() }
) { /* composable */ }
애니메이션 매개변수를 composable()에 직접 전달하여 각 composable에 대해 개별적으로 애니메이션을 재정의할 수 있습니다. 중요한 점은 애니메이션이 시스템 백 프레스 애니메이션과 충돌하지 않아야 한다는 것입니다. 공유 요소 전환을 위해서는 accompanist-navigation-animation 라이브러리 또는 Modifier.graphicsLayer를 통한 사용자 지정 구현이 필요합니다. Android Developers(2025)에 따르면, 프로덕션 애플리케이션의 80%가 표준 네비게이션에 수평 슬라이드 애니메이션을 사용합니다.
자주 묻는 질문
Navigation Compose는 Fragment 없이 작동하며, 그래프에 composable 함수와 Kotlin DSL을 사용합니다. Navigation Component(View)는 FragmentManager와 XML 그래프를 기반으로 합니다. Compose 버전은 더 간단하고 빠르며 Fragment 라이프사이클이 없습니다. View용 Navigation Component는 하이브리드 애플리케이션에만 적합합니다.
객체 ID를 전달하고 SavedStateHandle과 함께 ViewModel을 통해 데이터를 로드하는 것이 좋습니다. 객체가 간단한 경우 kotlinx.parcelize를 통해 Parcelable을 사용하세요. 인수(Bundle)를 통해 큰 객체를 직접 전달하는 것은 ~1MB로 제한되며 TransactionTooLargeException이 발생할 수 있습니다.
네, NavHost 내부의 navigation(route, startDestination) 함수를 통해 지원합니다. 중첩된 그래프는 자체 startDestination을 가지며 공통 경로 접두사 아래 결합됩니다. 이를 통해 각 기능 모듈에 대해 격리된 그래프를 사용하는 모듈식 아키텍처를 구성할 수 있습니다.
NavController는 Compose의 BackHandler를 통해 백 프레스를 자동으로 처리합니다. 뒤로 가기를 누르면 navController.popBackStack()을 호출하세요. 사용자 지정 처리(종료 확인)의 경우, popBackStack()을 호출하기 전에 BackHandler(enabled = condition) { callback }을 사용하세요.
현재 Navigation Compose는 Compose Multiplatform에서 지원되지 않습니다. 크로스 플랫폼 프로젝트의 iOS 부분에는 Voyager 또는 Decompose를 사용하세요. Google은 KMP 지원을 위해 작업 중이지만 출시 일정은 없습니다. Android 전용 프로젝트의 경우 Navigation Compose가 유일한 권장 옵션입니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.