-
Navigation 3 설명회Android📱 2026. 7. 6. 19:20
Jetpack Navigation 3 (Nav3) 완벽 정리
Navigation 3는 Jetpack Compose 전용으로 처음부터 다시 설계된 내비게이션 라이브러리입니다.
2025년 11월에 정식(stable) 으로 승격됐고, 이 글 기준 최신 버전은 1.1.4 입니다.
1. 왜 Nav3인가?
Navigation 2 (navigation-compose) Navigation 3 (Nav3)
백스택 소유 라이브러리가 관리 (불투명) 개발자가 소유 (List<NavKey>) 상태 모델 NavController 내부 상태 Compose State 기반 경로 표현 문자열 route / 타입 세이프 route NavKey 객체 (직렬화 가능) 백스택 조작 navigate(), popBackStack() backStack.add(), backStack.removeLastOrNull() — 그냥 리스트 조작 멀티 백스택/적응형 별도 처리 필요 Scene 전략으로 내장 지원 (여러 화면 동시 표시) 핵심 철학은 "백스택은 상태(state)다" 입니다.
리스트에 key를 넣으면 화면이 push 되고, 빼면 pop 하는 방식으로 조절합니다
2. 의존성 설정
libs.versions.toml
[versions] nav3Core = "1.1.4" lifecycleViewmodelNav3 = "2.11.0" kotlinSerialization = "2.2.21" kotlinxSerializationCore = "1.9.0" material3AdaptiveNav3 = "1.3.0-rc01" [libraries] androidx-navigation3-runtime = { module = "androidx.navigation3:navigation3-runtime", version.ref = "nav3Core" } androidx-navigation3-ui = { module = "androidx.navigation3:navigation3-ui", version.ref = "nav3Core" } androidx-lifecycle-viewmodel-navigation3 = { module = "androidx.lifecycle:lifecycle-viewmodel-navigation3", version.ref = "lifecycleViewmodelNav3" } kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinxSerializationCore" } androidx-material3-adaptive-navigation3 = { group = "androidx.compose.material3.adaptive", name = "adaptive-navigation3", version.ref = "material3AdaptiveNav3" } [plugins] jetbrains-kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlinSerialization" }app/build.gradle.kts
plugins { alias(libs.plugins.jetbrains.kotlin.serialization) } dependencies { implementation(libs.androidx.navigation3.ui) implementation(libs.androidx.navigation3.runtime) implementation(libs.androidx.lifecycle.viewmodel.navigation3) // ViewModel 스코핑 implementation(libs.androidx.material3.adaptive.navigation3) // 적응형 레이아웃(선택) implementation(libs.kotlinx.serialization.core) }kotlin.plugin.serialization이 필요한 이유는, 경로(NavKey)를 직렬화해서 프로세스 종료 후에도 백스택을 복원하기 위해서입니다.
3. 핵심 구성요소 4가지
Nav3는 딱 4개의 개념만 이해하면 됩니다.
NavKey → 화면 하나를 가리키는 "주소" (직렬화 가능한 객체) back stack → NavKey들의 List. 내가 직접 add / remove NavEntry → NavKey를 실제 Composable 콘텐츠로 연결한 항목 NavDisplay → back stack을 관찰해서 화면을 그려주는 Composable동작 흐름:
[backStack: List<NavKey>] ──관찰──▶ [NavDisplay] ──entryProvider──▶ [화면 Composable] ▲ │ backStack.add(...) / removeLastOrNull() 내가 직접 조작
4. 가장 기본 예시
4-1. 경로(NavKey) 정의
각 화면을 NavKey를 구현하는 @Serializable 객체로 정의합니다. 파라미터가 없으면 data object, 있으면 data class.
import androidx.navigation3.runtime.NavKey import kotlinx.serialization.Serializable @Serializable data object Home : NavKey @Serializable data class Product(val id: String) : NavKey4-2. 백스택 만들고 NavDisplay에 연결
import androidx.compose.runtime.Composable import androidx.navigation3.runtime.NavEntry import androidx.navigation3.runtime.rememberNavBackStack import androidx.navigation3.ui.NavDisplay @Composable fun NavExample() { // 시작 화면을 지정해 백스택 생성 (상태 복원까지 처리됨) val backStack = rememberNavBackStack(Home) NavDisplay( backStack = backStack, onBack = { backStack.removeLastOrNull() }, // 뒤로가기 = 리스트 마지막 제거 entryProvider = { key -> when (key) { is Home -> NavEntry(key) { Column { Text("Welcome to Nav3") Button(onClick = { // 화면 이동 = 리스트에 추가 backStack.add(Product("123")) }) { Text("상품 상세로 이동") } } } is Product -> NavEntry(key) { Text("Product ${key.id}") } else -> NavEntry(Unit) { Text("Unknown route") } } } ) }포인트
- 화면 이동: backStack.add(Product("123"))
- 뒤로 가기: backStack.removeLastOrNull()
- NavController도, navigate("route/123") 문자열도 없습니다. 그냥 리스트를 다루는 겁니다.
5. entryProvider DSL로 깔끔하게
위처럼 when 문으로 직접 매핑할 수도 있지만, 화면이 많아지면 DSL이 훨씬 읽기 좋습니다.
import androidx.navigation3.runtime.entryProvider NavDisplay( backStack = backStack, onBack = { backStack.removeLastOrNull() }, entryProvider = entryProvider { entry<Home> { HomeScreen(onProductClick = { id -> backStack.add(Product(id)) }) } entry<Product> { key -> // key가 타입 세이프하게 주입됨 ProductScreen(id = key.id) } } )entry<타입> { key -> ... } 형태라서 캐스팅 없이 파라미터에 바로 접근할 수 있습니다.
메타데이터를 붙이고 싶다면:
entry<Product>( metadata = mapOf("extraDataKey" to "extraDataValue") ) { key -> ProductScreen(id = key.id) }
6. ViewModel & 상태 유지 — entryDecorators
각 화면에 ViewModel을 스코프하거나, 백스택에 남아 있는 동안 상태를 보존하려면 decorator를 추가합니다. 순서가 중요합니다.
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator @Composable fun MyNavigation(backStack: NavBackStack<NavKey>) { NavDisplay( backStack = backStack, entryDecorators = listOf( // 1) 상태 저장/복원 (rememberSaveable 등) rememberSaveableStateHolderNavEntryDecorator(), // 2) 각 NavEntry에 ViewModelStore 연결 rememberViewModelStoreNavEntryDecorator(), ), entryProvider = entryProvider { entry<Home> { HomeScreen() } entry<Product> { key -> ProductScreen(key.id) } } ) }이렇게 하면 entry<Product> 블록 안에서 viewModel()로 얻은 ViewModel이 해당 화면이 백스택에 있는 동안만 살아있고, pop 되면 자동으로 정리됩니다.
7. 적응형 레이아웃 (Scene 전략)
Nav3의 킬러 기능. NavDisplay는 백스택의 여러 항목을 동시에 화면에 표시할 수 있습니다. 태블릿/폴더블에서 리스트-디테일을 나란히 보여주는 것도 adaptive-navigation3의 Scene 전략으로 선언적으로 처리합니다. (별도 백스택을 손수 관리하던 Nav2 대비 큰 개선)
// adaptive-navigation3 가 제공하는 ListDetailSceneStrategy 등을 // NavDisplay(sceneStrategy = ...) 로 넘겨 창 크기에 따라 자동 전환화면 크기에 따라 "한 화면 vs 두 화면"이 자동으로 바뀌면서도, 백스택은 여전히 하나의 리스트로 유지됩니다.
8. 정리
- 백스택 = 내가 소유한 List<NavKey> 라는 한 문장이 Nav3의 전부입니다.
- 이동은 add, 뒤로는 removeLastOrNull. 상태 모델이 곧 내비게이션입니다.
- NavKey(@Serializable) → NavDisplay + entryProvider → 화면. 이 3단계만 기억하세요.
- ViewModel/상태 보존은 entryDecorators로, 적응형 UI는 Scene 전략으로.
Compose의 "상태로 UI를 그린다"는 철학이 마침내 내비게이션까지 일관되게 확장된 셈입니다.
참고 자료
'Android📱' 카테고리의 다른 글
Jetpack Compose Side Effects 파헤치기 — RememberObserver로 이해하는 effect 핸들러 (1) 2026.08.01 ComposeView가 언제 죽는지 아세요? — ViewCompositionStrategy 파헤치기 (0) 2026.07.10 [Android] pendingIntent putExtra 및 주의 사항 (0) 2024.09.12 [Android] Sensor를 이용하여 방위각 구해보기~~ (1) 2024.06.04 [Android] kotlin + gRpc 연결하기 (0) 2024.04.23