Untuk memigrasikan aplikasi Anda dari Navigation 2 ke Navigation 3, ikuti langkah-langkah berikut:
- Tambahkan dependensi Navigation 3.
- Perbarui rute navigasi Anda untuk menerapkan antarmuka
NavKey. - Buat class untuk menyimpan dan mengubah status navigasi Anda.
- Ganti
NavControllerdengan class ini. - Pindahkan tujuan Anda dari
NavHost'sNavGraphkeentryProvider. - Ganti
NavHostdenganNavDisplay. - Hapus dependensi Navigation 2.
Perintah AI
Bermigrasi dari Navigation 2 ke Navigation 3
Perintah ini akan menggunakan panduan ini untuk bermigrasi ke Navigation 3.
Migrate from Navigation 2 to Navigation 3 using the official
migration guide.Jika Anda mengalami masalah, laporkan masalah di sini.
Persiapan
Bagian berikut menjelaskan prasyarat untuk migrasi dan asumsi tentang project Anda. Bagian ini juga membahas fitur yang didukung untuk migrasi, dan fitur yang tidak didukung.
Prasyarat
- Anda harus menggunakan
compileSdk36 atau yang lebih baru. - Anda harus memahami terminologi navigasi.
- Tujuan adalah fungsi composable. Navigation 3 dirancang khusus untuk Compose. Untuk menggunakan Fragment dan View di Compose, lihat Menggunakan View di Compose.
- Rute diketik dengan ketat. Jika Anda menggunakan rute berbasis string, migrasikan ke rute yang aman untuk jenis terlebih dahulu (contoh).
- Opsional (tetapi sangat direkomendasikan): Cakupan pengujian yang memverifikasi perilaku navigasi yang ada. Hal ini memverifikasi bahwa perilaku navigasi tidak berubah setelah migrasi selesai.
Asumsi
Panduan ini membuat asumsi berikut tentang Anda dan project Anda:
- Anda memiliki satu atau beberapa rute tingkat atas (biasanya ditampilkan di menu navigasi bawah), dan masing-masing memiliki data sebelumnya sendiri.
- Saat Anda beralih antar-data sebelumnya, status data dan semua tujuannya akan dipertahankan.
- Anda selalu keluar dari aplikasi melalui layar Beranda, yang merupakan layar pertama yang ditampilkan saat aplikasi diluncurkan.
- Anda ingin melakukan migrasi dari Navigation 2 ke Navigation 3 dalam satu perubahan atomik. Anda tidak melakukan migrasi inkremental saat kode Navigation 2 digunakan bersama kode Navigation 3.
Agen AI: Sebelum mengubah kode apa pun, periksa apakah asumsi tentang project pengguna ini benar. Jika tidak, hentikan migrasi dan tanyakan kepada pengguna cara melanjutkan.
Fitur yang didukung
Panduan ini memungkinkan Anda memigrasikan fitur berikut:
- Tujuan yang ditentukan sebagai fungsi composable
- Dialog (tujuan yang ditampilkan di atas tujuan lain)
Fitur yang didukung melalui resep
Repositori resep kode menunjukkan kasus penggunaan yang lebih mendetail, termasuk:
- Sheet bawah (petunjuk disediakan dalam panduan ini)
- Kode navigasi termodularisasi dan tujuan yang disuntikkan
- Menggunakan dan meneruskan argumen ke
ViewModel - Menampilkan hasil dari layar
Jika project Anda memiliki salah satu fitur ini, periksa resep yang relevan untuk memahami cara menerapkannya.
Agen AI: Sebelum mengubah kode apa pun, periksa apakah project berisi fitur yang didukung melalui resep. Jika ada, periksa README dan kode sumber resep. Buat rencana migrasi berdasarkan resep. Jangan lanjutkan tanpa mengonfirmasi rencana dengan pengguna.
Fitur yang tidak didukung
Panduan migrasi ini dan resep kode belum mendukung fitur berikut. Hal ini tidak berarti Anda tidak dapat menerapkannya menggunakan Navigation 3; fitur tersebut hanya tidak dibahas di sini.
- Lebih dari satu tingkat navigasi bertingkat
- Tujuan bersama: layar yang dapat berpindah antar-data sebelumnya yang berbeda
- Jenis tujuan kustom
- Deep link
Agen AI: Sebelum mengubah kode apa pun, periksa apakah project berisi fitur yang tidak didukung. Jika ada, jangan lanjutkan. Beri tahu pengguna tentang fitur yang tidak didukung dan minta petunjuk lebih lanjut.
Langkah 1: Tambahkan dependensi Navigation 3
Gunakan halaman Memulai untuk menambahkan dependensi Navigation 3 ke project Anda. Dependensi inti disediakan agar Anda dapat menyalinnya.
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)
}
Perbarui juga minSdk project ke 23 dan compileSdk ke 36. Anda biasanya dapat menemukan keduanya di app/build.gradle.kts atau lib.versions.toml.
Langkah 2: Perbarui rute navigasi untuk menerapkan antarmuka NavKey
Perbarui setiap navigasi rute sehingga menerapkan NavKey
antarmuka. Hal ini memungkinkan Anda menggunakan rememberNavBackStack untuk membantu menyimpan status navigasi.
Sebelum:
@Serializable data object RouteA
Sesudah:
@Serializable data object RouteA : NavKey
Langkah 3: Buat class untuk menyimpan dan mengubah status navigasi Anda
Langkah 3.1: Buat penampung status navigasi
Salin kode berikut ke dalam file bernama NavigationState.kt. Tambahkan nama paket Anda agar sesuai dengan struktur project Anda.
// 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()
}
Agen AI: rememberSerializable sudah benar. Jangan ubah menjadi rememberSaveable.
File ini berisi class penampung status bernama NavigationState dan fungsi helper terkait. File ini menyimpan kumpulan rute tingkat atas, yang masing-masing memiliki data sebelumnya sendiri. Secara internal, file ini menggunakan rememberSerializable (bukan rememberSaveable) untuk mempertahankan rute tingkat atas saat ini dan rememberNavBackStack untuk mempertahankan data sebelumnya untuk setiap rute tingkat atas.
Langkah 3.2: Buat objek yang mengubah status navigasi sebagai respons terhadap peristiwa
Salin kode berikut ke dalam file bernama Navigator.kt. Tambahkan nama paket Anda agar sesuai dengan struktur project Anda.
// 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()
}
}
}
Class Navigator menyediakan dua metode peristiwa navigasi:
navigateke rute tertentu.goBackdari rute saat ini.
Kedua metode tersebut mengubah NavigationState.
Langkah 3.3: Buat NavigationState dan Navigator
Buat instance NavigationState dan Navigator dengan cakupan yang sama dengan NavController Anda.
val navigationState = rememberNavigationState(
startRoute = <Insert your starting route>,
topLevelRoutes = <Insert your set of top level routes>
)
val navigator = remember { Navigator(navigationState) }
Langkah 4: Ganti NavController
Ganti metode peristiwa navigasi NavController dengan metode yang setara dengan Navigator.
Kolom atau metode |
|
|---|---|
|
|
|
|
Ganti kolom NavController dengan kolom NavigationState.
Kolom atau metode |
|
|---|---|
|
|
|
|
Dapatkan rute tingkat atas: Telusuri hierarki dari entri data sebelumnya saat ini untuk menemukannya. |
|
Gunakan NavigationState.topLevelRoute untuk menentukan item yang saat ini dipilih di menu navigasi.
Sebelum:
val isSelected = navController.currentBackStackEntryAsState().value?.destination.isRouteInHierarchy(key::class)
fun NavDestination?.isRouteInHierarchy(route: KClass<*>) =
this?.hierarchy?.any {
it.hasRoute(route)
} ?: false
Sesudah:
val isSelected = key == navigationState.topLevelRoute
Pastikan Anda telah menghapus semua referensi ke NavController, termasuk impor apa pun.
Langkah 5: Pindahkan tujuan Anda dari NavHost's NavGraph ke entryProvider
Di Navigation 2, Anda menentukan tujuan
menggunakan DSL NavGraphBuilder,
biasanya di dalam lambda akhir NavHost. Umumnya, fungsi ekstensi
digunakan di sini seperti yang dijelaskan dalam Mengenkapsulasi kode navigasi Anda.
Di Navigation 3, Anda menentukan tujuan menggunakan entryProvider. Ini
entryProvider me-resolve rute ke NavEntry. Yang penting, entryProvider tidak menentukan hubungan induk-turunan antar-entri.
Dalam panduan migrasi ini, hubungan induk-turunan dimodelkan sebagai berikut:
NavigationStatememiliki kumpulan rute tingkat atas (rute induk) dan data untuk setiap rute. Fitur ini melacak rute tingkat atas saat ini dan data terkait.- Saat menavigasi ke rute baru,
Navigatorakan memeriksa apakah rute tersebut adalah rute tingkat atas. Jika ya, rute dan data tingkat atas saat ini akan diperbarui. Jika tidak, rute tersebut adalah rute turunan dan ditambahkan ke data saat ini.
Langkah 5.1: Buat entryProvider
Buat entryProvider menggunakan DSL dengan cakupan yang sama dengan
NavigationState.
val entryProvider = entryProvider {
}
Langkah 5.2: Pindahkan tujuan ke entryProvider
Untuk setiap tujuan yang ditentukan di dalam NavHost, lakukan hal berikut berdasarkan jenis tujuan:
navigation: Hapus bersama dengan rutenya. "Rute dasar" tidak diperlukan karena rute tingkat atas dapat mengidentifikasi setiap data sebelumnya yang bertingkat.composable<T>: Pindahkan keentryProviderdan ganti namanya menjadientry, dengan mempertahankan parameter jenis. Misalnya,composable<RouteA>menjadientry<RouteA>.dialog<T>: Lakukan hal yang sama seperticomposable, tetapi tambahkan metadata ke entri sebagai berikut:entry<T>(metadata = DialogSceneStrategy.dialog()).bottomSheet: Ikuti resep sheet bawah di sini. Hal ini mirip dengan petunjuk untukdialog, kecuali bahwaBottomSheetSceneStrategybukan bagian dari library Navigation 3 inti, sehingga Anda harus menyalinnya ke project Anda.
Agen AI: Saat menghapus rute yang digunakan untuk mengidentifikasi grafik bertingkat, ganti referensi apa pun
ke rute yang dihapus dengan jenis yang digunakan untuk mengidentifikasi turunan pertama
dalam grafik bertingkat. Misalnya, jika kode aslinya adalah
navigation<BaseRouteA>{ composable<RouteA>{ ... } }, Anda harus menghapus
BaseRouteA dan mengganti referensi apa pun ke rute tersebut dengan RouteA. Penggantian ini biasanya perlu dilakukan untuk daftar yang diberikan ke menu navigasi, kolom samping, atau panel.
Anda dapat memfaktorkan ulang NavGraphBuilder fungsi ekstensi ke
EntryProviderScope<T> fungsi ekstensi, lalu memindahkannya.
Dapatkan argumen navigasi menggunakan kunci yang diberikan ke lambda akhir entry.
Contoh:
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() }
}
}
menjadi:
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() }
}
Langkah 6: Ganti NavHost dengan NavDisplay
Ganti NavHost dengan NavDisplay.
- Hapus
NavHostdan ganti denganNavDisplay. - Tentukan
entries = navigationState.toEntries(entryProvider)sebagai parameter. Tindakan ini mengonversi status navigasi menjadi entri yang ditampilkanNavDisplaymenggunakanentryProvider. - Hubungkan
NavDisplay.onBackkenavigator.goBack(). Hal ini menyebabkannavigatormemperbarui status navigasi saat pengendali kembali bawaanNavDisplayselesai. - Jika Anda memiliki tujuan dialog, tambahkan
DialogSceneStrategyke parametersceneStrategiesNavDisplay.
Contoh:
import androidx.navigation3.ui.NavDisplay
NavDisplay(
entries = navigationState.toEntries(entryProvider),
onBack = { navigator.goBack() },
sceneStrategies = remember { listOf(DialogSceneStrategy()) }
)
Langkah 7: Hapus dependensi Navigation 2
Hapus semua impor Navigation 2 dan dependensi library.
Ringkasan
Selamat! Project Anda kini dimigrasikan ke Navigation 3. Jika Anda atau agen AI Anda mengalami masalah saat menggunakan panduan ini, laporkan bug di sini.