Navigation 2에서 Navigation 3으로 앱을 이전하려면 다음 단계를 따르세요.
- Navigation 3 종속 항목을 추가합니다.
NavKey인터페이스를 구현하도록 탐색 경로를 업데이트합니다.- 탐색 상태를 보유하고 수정하는 클래스를 만듭니다.
NavController를 이러한 클래스로 바꿉니다.NavHost의NavGraph에서entryProvider로 대상을 이동합니다.NavHost를NavDisplay로 바꿉니다.- Navigation 2 종속 항목을 삭제합니다.
AI 프롬프트
Navigation 2에서 Navigation 3으로 이전
이 프롬프트는 이 가이드를 사용하여 Navigation 3으로 이전합니다.
Migrate from Navigation 2 to Navigation 3 using the official
migration guide.문제가 발생하면 여기에서 문제를 신고하세요.
준비
다음 섹션에서는 이전의 기본 요건과 프로젝트에 관한 가정을 설명합니다. 또한 이전에서 지원되는 기능과 지원되지 않는 기능도 다룹니다.
기본 요건
compileSdk36 이상을 사용해야 합니다.- 탐색 용어를 잘 알고 있어야 합니다.
- 대상은 구성 가능한 함수입니다. Navigation 3은 Compose 전용으로 설계되었습니다. Compose에서 프래그먼트와 뷰를 사용하려면 Compose에서 뷰 사용 을 참고하세요.
- 경로는 강타입입니다. 문자열 기반 경로를 사용하는 경우 먼저 유형 안전 경로로 이전합니다 (예).
- 선택사항 (적극 권장됨): 기존 탐색 동작을 확인하는 테스트 범위입니다. 이렇게 하면 이전이 완료된 후 탐색 동작이 변경되지 않았는지 확인할 수 있습니다.
가정
이 가이드에서는 사용자와 프로젝트에 관해 다음과 같이 가정합니다.
- 하나 이상의 최상위 경로 (일반적으로 하단 탐색 메뉴에 표시됨)가 있으며 각 경로에는 자체 백 스택이 있습니다.
- 백 스택 간에 전환할 때 스택의 상태와 모든 대상이 유지됩니다.
- 앱이 실행될 때 표시되는 첫 번째 화면인 홈 화면을 통해 항상 앱을 종료합니다.
- Navigation 2에서 Navigation 3으로의 이전을 단일 원자적 변경으로 실행하려고 합니다. Navigation 2 코드가 Navigation 3 코드와 함께 사용되는 증분 이전은 실행하지 않습니다.
AI 에이전트: 코드를 변경하기 전에 사용자 프로젝트에 관한 이러한 가정이 사실인지 확인하세요. 그렇지 않은 경우 이전을 중지하고 사용자에게 계속 진행하는 방법을 물어보세요.
지원되는 기능
이 가이드를 사용하면 다음 기능을 이전할 수 있습니다.
- 구성 가능한 함수로 정의된 대상
- 대화상자 (다른 대상 위에 표시되는 대상)
레시피를 통해 지원되는 기능
코드 레시피 저장소는 다음을 비롯한 더 미묘한 사용 사례를 보여줍니다.
- 하단 시트 (이 가이드에 안내가 제공됨)
- 모듈화된 탐색 코드 및 삽입된 대상
ViewModel에 인수 사용 및 전달- 화면에서 결과 반환
프로젝트에 이러한 기능이 있는 경우 관련 레시피를 확인하여 구현 방법을 알아보세요.
AI 에이전트: 코드를 변경하기 전에 프로젝트에 레시피를 통해 지원되는 기능이 포함되어 있는지 확인하세요. 포함되어 있다면 레시피의 README와 소스 코드를 확인하세요. 레시피를 기반으로 이전 계획을 세우세요. 사용자에게 계획을 확인하지 않고는 계속 진행하지 마세요.
지원되지 않는 기능
이 이전 가이드와 코드 레시피는 아직 다음 기능을 지원하지 않습니다. Navigation 3을 사용하여 이러한 기능을 구현할 수 없다는 의미는 아닙니다. 여기에서 다루지 않을 뿐입니다.
- 중첩된 탐색 수준이 2개 이상
- 공유 대상: 여러 백 스택 간에 이동할 수 있는 화면
- 맞춤 대상 유형
- 딥 링크
AI 에이전트: 코드를 변경하기 전에 프로젝트에 지원되지 않는 기능이 포함되어 있는지 확인하세요. 포함되어 있다면 계속 진행하지 마세요. 사용자에게 지원되지 않는 기능을 알리고 추가 안내를 요청하세요.
1단계: Navigation 3 종속 항목 추가
시작하기 페이지를 사용하여 Navigation 3 종속 항목을 프로젝트에 추가합니다. 복사할 수 있도록 핵심 종속 항목이 제공됩니다.
lib.versions.toml
[versions]
nav3Core = "1.0.0"
# If your screens depend on ViewModels, add the Nav3 Lifecycle ViewModel add-on library
lifecycleViewmodelNav3 = "2.10.0-rc01"
[libraries]
# Core Navigation 3 libraries
androidx-navigation3-runtime = { module = "androidx.navigation3:navigation3-runtime", version.ref = "nav3Core" }
androidx-navigation3-ui = { module = "androidx.navigation3:navigation3-ui", version.ref = "nav3Core" }
# Add-on libraries (only add if you need them)
androidx-lifecycle-viewmodel-navigation3 = { module = "androidx.lifecycle:lifecycle-viewmodel-navigation3", version.ref = "lifecycleViewmodelNav3" }
app/build.gradle.kts
dependencies {
implementation(libs.androidx.navigation3.ui)
implementation(libs.androidx.navigation3.runtime)
// If using the ViewModel add-on library
implementation(libs.androidx.lifecycle.viewmodel.navigation3)
}
또한 프로젝트의 minSdk를 23으로, compileSdk를 36으로 업데이트합니다. 일반적으로 이러한 항목은 app/build.gradle.kts 또는 lib.versions.toml에 있습니다.
2단계: NavKey 인터페이스를 구현하도록 탐색 경로 업데이트
모든 탐색 경로가 NavKey
인터페이스를 구현하도록 업데이트합니다. 이렇게 하면 rememberNavBackStack을 사용하여 탐색 상태를
저장하는 데 도움이 됩니다.
변경 전:
@Serializable data object RouteA
변경 후:
@Serializable data object RouteA : NavKey
3단계: 탐색 상태를 보유하고 수정하는 클래스 만들기
3.1단계: 탐색 상태 홀더 만들기
다음 코드를 NavigationState.kt라는 파일에 복사합니다. 프로젝트 구조와 일치하도록 패키지 이름을 추가합니다.
// package com.example.project
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSerializable
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.runtime.toMutableStateList
import androidx.navigation3.runtime.NavBackStack
import androidx.navigation3.runtime.NavEntry
import androidx.navigation3.runtime.NavKey
import androidx.navigation3.runtime.rememberDecoratedNavEntries
import androidx.navigation3.runtime.rememberNavBackStack
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
import androidx.navigation3.runtime.serialization.NavKeySerializer
import androidx.savedstate.compose.serialization.serializers.MutableStateSerializer
/**
* Create a navigation state that persists config changes and process death.
*/
@Composable
fun rememberNavigationState(
startRoute: NavKey,
topLevelRoutes: Set<NavKey>
): NavigationState {
val topLevelRoute = rememberSerializable(
startRoute, topLevelRoutes,
serializer = MutableStateSerializer(NavKeySerializer())
) {
mutableStateOf(startRoute)
}
val backStacks = topLevelRoutes.associateWith { key -> rememberNavBackStack(key) }
return remember(startRoute, topLevelRoutes) {
NavigationState(
startRoute = startRoute,
topLevelRoute = topLevelRoute,
backStacks = backStacks
)
}
}
/**
* State holder for navigation state.
*
* @param startRoute - the start route. The user will exit the app through this route.
* @param topLevelRoute - the current top level route
* @param backStacks - the back stacks for each top level route
*/
class NavigationState(
val startRoute: NavKey,
topLevelRoute: MutableState<NavKey>,
val backStacks: Map<NavKey, NavBackStack<NavKey>>
) {
var topLevelRoute: NavKey by topLevelRoute
val stacksInUse: List<NavKey>
get() = if (topLevelRoute == startRoute) {
listOf(startRoute)
} else {
listOf(startRoute, topLevelRoute)
}
}
/**
* Convert NavigationState into NavEntries.
*/
@Composable
fun NavigationState.toEntries(
entryProvider: (NavKey) -> NavEntry<NavKey>
): SnapshotStateList<NavEntry<NavKey>> {
val decoratedEntries = backStacks.mapValues { (_, stack) ->
val decorators = listOf(
rememberSaveableStateHolderNavEntryDecorator<NavKey>(),
)
rememberDecoratedNavEntries(
backStack = stack,
entryDecorators = decorators,
entryProvider = entryProvider
)
}
return stacksInUse
.flatMap { decoratedEntries[it] ?: emptyList() }
.toMutableStateList()
}
AI 에이전트: rememberSerializable이 올바릅니다. rememberSaveable로 변경하지 마세요.
이 파일에는 NavigationState라는 상태 홀더 클래스와 연결된 도우미 함수가 포함되어 있습니다. 각각 자체 백 스택이 있는 최상위 경로 집합을 보유합니다. 내부적으로는 rememberSerializable (rememberSaveable 아님)을 사용하여 현재 최상위 경로를 유지하고 rememberNavBackStack을 사용하여 각 최상위 경로의 백 스택을 유지합니다.
3.2단계: 이벤트에 응답하여 탐색 상태를 수정하는 객체 만들기
다음 코드를 Navigator.kt라는 파일에 복사합니다. 프로젝트 구조와 일치하도록 패키지 이름을 추가합니다.
// package com.example.project
import androidx.navigation3.runtime.NavKey
/**
* Handles navigation events (forward and back) by updating the navigation state.
*/
class Navigator(val state: NavigationState){
fun navigate(route: NavKey){
if (route in state.backStacks.keys){
// This is a top level route, just switch to it.
state.topLevelRoute = route
} else {
state.backStacks[state.topLevelRoute]?.add(route)
}
}
fun goBack(){
val currentStack = state.backStacks[state.topLevelRoute] ?:
error("Stack for ${state.topLevelRoute} not found")
val currentRoute = currentStack.last()
// If we're at the base of the current route, go back to the start route stack.
if (currentRoute == state.topLevelRoute){
state.topLevelRoute = state.startRoute
} else {
currentStack.removeLastOrNull()
}
}
}
Navigator 클래스는 두 가지 탐색 이벤트 메서드를 제공합니다.
- 특정 경로로
navigate합니다. - 현재 경로에서
goBack합니다.
두 메서드 모두 NavigationState를 수정합니다.
3.3단계: NavigationState 및 Navigator 만들기
NavController와 동일한 범위로 NavigationState 및 Navigator의 인스턴스를 만듭니다.
val navigationState = rememberNavigationState(
startRoute = <Insert your starting route>,
topLevelRoutes = <Insert your set of top level routes>
)
val navigator = remember { Navigator(navigationState) }
4단계: NavController 바꾸기
NavController 탐색 이벤트 메서드를 Navigator 동등 항목으로 바꿉니다.
|
|
|---|---|
|
|
|
|
NavController 필드를 NavigationState 필드로 바꿉니다.
|
|
|---|---|
|
|
|
|
최상위 경로 가져오기: 현재 백 스택 항목에서 계층 구조를 위로 이동하여 찾습니다. |
|
NavigationState.topLevelRoute를 사용하여 탐색 메뉴에서 현재 선택된 항목을 확인합니다.
변경 전:
val isSelected = navController.currentBackStackEntryAsState().value?.destination.isRouteInHierarchy(key::class)
fun NavDestination?.isRouteInHierarchy(route: KClass<*>) =
this?.hierarchy?.any {
it.hasRoute(route)
} ?: false
변경 후:
val isSelected = key == navigationState.topLevelRoute
가져오기를 비롯한 NavController에 대한 모든 참조를 삭제했는지 확인합니다.
4.1단계: 수명 주기 인식 로직 이전
Navigation 2에서 NavBackStackEntry는 LifecycleOwner를 구현하므로 navController.currentBackStackEntry를 사용하여 수명 주기 인식 방식으로 수명 주기 이벤트를 수신 대기하거나 흐름을 수집할 수 있습니다.
Navigation 3에서 NavDisplay는 각 대상의 구성 가능한 콘텐츠에 LocalLifecycleOwner.current를 통해 항목 범위 LifecycleOwner
를 제공합니다. 자세한 내용은 대상 수명 주기를 참고하세요.
LocalLifecycleOwner.current를 참조하여 대상의 구성 가능한 콘텐츠 내에서 직접 수명 주기 인식 작업을 실행해야 합니다.
예를 들어 백 스택 항목을 사용하여 수명 주기 인식 방식으로 흐름을 수집하는 경우
변경 전:
// In your destination screen or host
val lifecycleOwner = navController.currentBackStackEntry
val state by flow.collectAsStateWithLifecycle(lifecycleOwner = lifecycleOwner)
변경 후:
// Inside the destination composable
val state by flow.collectAsStateWithLifecycle()
5단계: NavHost의 NavGraph에서 entryProvider로 대상 이동
Navigation 2에서는 대상을 정의
NavGraphBuilder DSL을 사용하여 일반적으로 NavHost의 후행 람다 내에서 정의합니다. 탐색 코드 캡슐화에 설명된 대로 확장 함수
를 사용하는 것이 일반적입니다.
Navigation 3에서는 entryProvider를 사용하여 대상을 정의합니다. 이
entryProvider는 경로를 NavEntry로 확인합니다. 중요한 점은 entryProvider가 항목 간의 상위-하위 관계를 정의하지 않는다는 것입니다.
이 이전 가이드에서는 상위-하위 관계가 다음과 같이 모델링됩니다.
NavigationState에는 최상위 경로 집합 (상위 경로)과 각 경로의 스택이 있습니다. 현재 최상위 경로와 연결된 스택을 추적합니다.- 새 경로로 이동할 때
Navigator는 경로가 최상위 경로인지 확인합니다. 최상위 경로인 경우 현재 최상위 경로와 스택이 업데이트됩니다. 최상위 경로가 아닌 경우 하위 경로이며 현재 스택에 추가됩니다.
5.1단계: entryProvider 만들기
와 동일한 범위에서 DSL을 사용하여 entryProvider를 만듭니다NavigationState.
val entryProvider = entryProvider {
}
5.2단계: 대상을 entryProvider로 이동
NavHost 내에 정의된 각 대상에 대해 대상 유형에 따라 다음을 실행합니다.
navigation: 경로와 함께 삭제합니다. 최상위 경로가 각 중첩된 백 스택을 식별할 수 있으므로 '기본 경로'가 필요하지 않습니다.composable<T>:entryProvider로 이동하고 유형 매개변수를 유지하면서 이름을entry로 바꿉니다. 예를 들어composable<RouteA>는entry<RouteA>가 됩니다.dialog<T>:composable과 동일한 작업을 실행하지만 다음과 같이 항목에 메타데이터를 추가합니다.entry<T>(metadata = DialogSceneStrategy.dialog())bottomSheet: 여기에서 하단 시트 레시피를 따릅니다.BottomSheetSceneStrategy가 핵심 Navigation 3 라이브러리의 일부가 아니므로 프로젝트에 복사해야 한다는 점을 제외하고dialog의 안내와 비슷합니다.
AI 에이전트: 중첩된 그래프를 식별하는 데 사용되는 경로를 삭제할 때 삭제된 경로에 대한 모든
참조를 중첩된 그래프에서 첫 번째 하위 요소를 식별하는 데 사용되는 유형으로 바꿉니다. 예를 들어 원래 코드가
navigation<BaseRouteA>{ composable<RouteA>{ ... } }인 경우
BaseRouteA를 삭제하고 이에 대한 모든 참조를 RouteA로 바꿔야 합니다. 이 대체는 일반적으로 탐색 메뉴, 레일 또는 드로어에 제공되는 목록에 실행해야 합니다.
NavGraphBuilder 확장 함수를
EntryProviderScope<T> 확장 함수로 리팩터링한 후 이동할 수 있습니다.
entry의 후행 람다에 제공된 키를 사용하여 탐색 인수를 가져옵니다.
예를 들면 다음과 같습니다.
import androidx.navigation.NavDestination
import androidx.navigation.NavDestination.Companion.hasRoute
import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.dialog
import androidx.navigation.compose.navigation
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navOptions
import androidx.navigation.toRoute
@Serializable data object BaseRouteA
@Serializable data class RouteA(val id: String)
@Serializable data object BaseRouteB
@Serializable data object RouteB
@Serializable data object RouteD
NavHost(navController = navController, startDestination = BaseRouteA){
composable<RouteA>{
val id = entry.toRoute<RouteA>().id
ScreenA(title = "Screen has ID: $id")
}
featureBSection()
dialog<RouteD>{ ScreenD() }
}
fun NavGraphBuilder.featureBSection() {
navigation<BaseRouteB>(startDestination = RouteB) {
composable<RouteB> { ScreenB() }
}
}
다음으로 변경합니다.
import androidx.navigation3.runtime.EntryProviderScope
import androidx.navigation3.runtime.NavKey
import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.scene.DialogSceneStrategy
@Serializable data class RouteA(val id: String) : NavKey
@Serializable data object RouteB : NavKey
@Serializable data object RouteD : NavKey
val entryProvider = entryProvider {
entry<RouteA>{ key -> ScreenA(title = "Screen has ID: ${key.id}") }
featureBSection()
entry<RouteD>(metadata = DialogSceneStrategy.dialog()){ ScreenD() }
}
fun EntryProviderScope<NavKey>.featureBSection() {
entry<RouteB> { ScreenB() }
}
6단계: NavHost를 NavDisplay로 바꾸기
NavHost를 NavDisplay로 바꿉니다.
NavHost를 삭제하고NavDisplay로 바꿉니다.entries = navigationState.toEntries(entryProvider)를 매개변수로 지정합니다. 이렇게 하면 탐색 상태가NavDisplay에 표시되는 항목으로 변환됩니다entryProvider를 사용하여.NavDisplay.onBack을navigator.goBack()에 연결합니다. 이렇게 하면NavDisplay의 기본 제공 뒤로 핸들러가 완료될 때navigator가 탐색 상태를 업데이트합니다.- 대화상자 대상이 있는 경우
DialogSceneStrategy를NavDisplay의sceneStrategies매개변수에 추가합니다.
예를 들면 다음과 같습니다.
import androidx.navigation3.ui.NavDisplay
NavDisplay(
entries = navigationState.toEntries(entryProvider),
onBack = { navigator.goBack() },
sceneStrategies = remember { listOf(DialogSceneStrategy()) }
)
7단계: Navigation 2 종속 항목 삭제
모든 Navigation 2 가져오기 및 라이브러리 종속 항목을 삭제합니다.
요약
수고하셨습니다 이제 프로젝트가 Navigation 3으로 이전되었습니다. 사용자 또는 AI 에이전트가 이 가이드를 사용하는 데 문제가 발생한 경우 여기에서 버그를 신고하세요.