Android becerileri
GitHub'da gösterJetpack Navigation 3
android skills add navigation-3Uygulamanızı Navigation 2'den Navigation 3'e taşımak için aşağıdaki adımları uygulayın:
- Navigation 3 bağımlılıklarını ekleyin.
NavKeyarayüzünü uygulamak için navigasyon rotalarınızı güncelleyin.- Navigasyon durumunuzu korumak ve değiştirmek için sınıflar oluşturun.
NavControlleryerine bu sınıfları kullanın.- Hedeflerinizi
NavHostadlı kullanıcınınNavGraphklasöründen birentryProviderklasörüne taşıyın. NavHostyerineNavDisplaykoyun.- Navigation 2 bağımlılıklarını kaldırın.
Hazırlık
Aşağıdaki bölümlerde, taşıma işlemi için ön koşullar ve projenizle ilgili varsayımlar açıklanmaktadır. Ayrıca, taşıma için desteklenen ve desteklenmeyen özellikler de ele alınır.
Ön koşullar
compileSdk36 veya daha yeni bir sürüm kullanmanız gerekir.- Navigasyon terminolojisi hakkında bilgi sahibi olmanız gerekir.
- Hedefler, composable işlevlerdir. Navigation 3, Compose için özel olarak tasarlanmıştır. Compose'da parçaları ve görünümleri kullanmak için Compose'da görünümleri kullanma başlıklı makaleyi inceleyin.
- Rotalar kesin olarak belirlenmiştir. Dize tabanlı rotalar kullanıyorsanız önce tip güvenli rotalara geçiş yapın (örnek).
- İsteğe bağlı (ancak kesinlikle önerilir): Mevcut gezinme davranışını doğrulayan test kapsamı. Bu sayede, taşıma işlemi tamamlandıktan sonra gezinme davranışının değişmediği doğrulanır.
Varsayımlar
Bu kılavuzda, sizin ve projeniz hakkında aşağıdaki varsayımlar yapılmaktadır:
- Bir veya daha fazla üst düzey rotanız (genellikle alt gezinme çubuğunda gösterilir) vardır ve her birinin kendi eski yığını bulunur.
- Eski yığınlar arasında geçiş yaptığınızda yığının ve tüm hedeflerinin durumu korunur.
- Uygulamadan her zaman, uygulama başlatıldığında görüntülenen ilk ekran olan Ana ekrandan çıkarsınız.
- Navigation 2'den Navigation 3'e geçişi tek bir atomik değişiklikle gerçekleştirmek istiyorsunuz. Navigation 2 kodunun Navigation 3 koduyla birlikte kullanıldığı artımlı bir taşıma işlemi gerçekleştirmiyorsunuz.
Yapay Zeka Ajanı: Kodda herhangi bir değişiklik yapmadan önce kullanıcının projesiyle ilgili bu varsayımların doğru olup olmadığını kontrol edin. Aksi takdirde taşıma işlemini durdurun ve kullanıcıya nasıl devam edeceğini sorun.
Desteklenen özellikler
Bu kılavuz, aşağıdaki özellikleri taşımanıza olanak tanır:
- Oluşturulabilir işlevler olarak tanımlanan hedefler
- İletişim kutuları (başka bir hedefin üzerinde gösterilen hedef)
Tarifler aracılığıyla desteklenen özellikler
Kod tarifleri deposu, aşağıdakiler de dahil olmak üzere daha ayrıntılı kullanım alanlarını gösterir:
- Alt sayfalar (talimatlar bu kılavuzda verilmiştir)
- Modüler gezinme kodu ve eklenen hedefler
ViewModeliçin bağımsız değişkenleri kullanma ve iletme- Bir ekrandan sonuç döndürme
Projenizde bu özelliklerden herhangi biri varsa nasıl uygulanacağını anlamak için ilgili tarifi inceleyin.
Yapay Zeka Ajanı: Herhangi bir kodda değişiklik yapmadan önce projede tariflerle desteklenen özellikler olup olmadığını kontrol edin. Varsa tarifin README dosyasını ve kaynak kodunu kontrol edin. Tarife dayalı bir taşıma planı oluşturun. Planı kullanıcıyla onaylamadan devam etmeyin.
Desteklenmeyen özellikler
Bu geçiş rehberi ve kod tarifleri aşağıdaki özellikleri henüz desteklememektedir. Bu, Navigation 3'ü kullanarak bunları uygulayamayacağınız anlamına gelmez. Sadece burada ele alınmamıştır.
- Birden fazla iç içe yerleştirilmiş gezinme düzeyi
- Paylaşılan hedefler: Farklı eski yığınlar arasında taşınabilen ekranlar
- Özel hedef türleri
Yapay Zeka Ajanı: Herhangi bir kodda değişiklik yapmadan önce projenin desteklenmeyen özellikler içerip içermediğini kontrol edin. Eşleşme varsa devam etmeyin. Kullanıcıyı desteklenmeyen özellik hakkında bilgilendirin ve daha fazla talimat isteyin.
1. adım: Navigation 3 bağımlılıklarını ekleyin
Navigation 3 bağımlılıklarını projenize eklemek için Başlayın sayfasını kullanın. Temel bağımlılıklar kopyalamanız için sağlanmıştır.
lib.versions.toml
[versions]
nav3Core = "1.2.0"
# If your screens depend on ViewModels, add the Nav3 Lifecycle ViewModel add-on library
lifecycleViewmodelNav3 = "2.11.0"
[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)
}
Ayrıca projenin minSdk değerini 23, compileSdk değerini ise 36 olarak güncelleyin. Bunları genellikle app/build.gradle.kts veya lib.versions.toml içinde bulabilirsiniz.
2. adım: NavKey arayüzünü uygulamak için gezinme rotalarını güncelleyin
Her gezinme rotasını, NavKey arayüzünü uygulayacak şekilde güncelleyin. Bu sayede, gezinme durumunuzu kaydetmenize yardımcı olması için rememberNavBackStack uygulamasını kullanabilirsiniz.
Önce:
@Serializable data object RouteA
Sonra:
@Serializable data object RouteA : NavKey
3. adım: Gezinme durumunuzu tutmak ve değiştirmek için sınıflar oluşturun
3.1. adım: Gezinme durumu bilgisi depolayıcı oluşturma
Aşağıdaki kodu NavigationState.kt adlı bir dosyaya kopyalayın. Proje yapınıza uygun bir paket adı ekleyin.
// 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() }
Yapay zeka ajanı: rememberSerializable doğru. rememberSaveable olarak değiştirmeyin.
Bu dosya, NavigationState adlı bir durum bilgisi depolayıcı sınıfı ve ilişkili yardımcı işlevleri içerir. Her birinin kendi eski yığını olan bir dizi üst düzey rota içerir. Dahili olarak, mevcut üst düzey rotayı kalıcı hale getirmek için rememberSerializable (rememberSaveable değil) ve her üst düzey rota için eski yığınları kalıcı hale getirmek için rememberNavBackStack kullanır.
3.2. adım: Etkinliklere yanıt olarak gezinme durumunu değiştiren bir nesne oluşturun
Aşağıdaki kodu Navigator.kt adlı bir dosyaya kopyalayın. Proje yapınızla eşleşecek şekilde paket adınızı ekleyin.
// 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 sınıfı, iki gezinme etkinliği yöntemi sağlar:
- Belirli bir rotaya
navigate. - Mevcut rotadan
goBackuzakta.
Her iki yöntem de NavigationState öğesini değiştirir.
3.3. adım: NavigationState ve Navigator oluşturun
NavController ile aynı kapsama sahip NavigationState ve Navigator örnekleri oluşturun.
val navigationState = rememberNavigationState( // ... startRoute = <Insert your starting route>, topLevelRoutes = <Insert your set of top level routes> // ... ) val navigator = remember { Navigator(navigationState) }
4. adım: NavController değiştirin
NavController gezinme etkinliği yöntemlerini Navigator eşdeğerleriyle değiştirin.
|
|
|---|---|
|
|
|
|
|
|
NavController alanlarını NavigationState alanlarıyla değiştirin.
|
|
|---|---|
|
|
|
|
|
|
|
|
Üst düzey rotayı alın: Bulmak için mevcut eski yığın girişinden hiyerarşide yukarı doğru ilerleyin. |
|
Gezinme çubuğunda seçili olan öğeyi belirlemek için NavigationState.topLevelRoute kullanın.
Önce:
// ... val isSelected = navController.currentBackStackEntryAsState().value?.destination.isRouteInHierarchy(key::class) // ... fun NavDestination?.isRouteInHierarchy(route: KClass<*>) = this?.hierarchy?.any { it.hasRoute(route) } ?: false
Sonra:
val isSelected = key == navigationState.topLevelRoute
İçe aktarmalar da dahil olmak üzere NavController ile ilgili tüm referansları kaldırdığınızı doğrulayın.
4.1. adım: Yaşam döngüsüne duyarlı mantığı taşıma
Navigation 2'de NavBackStackEntry, LifecycleOwner'i uygulayarak navController.currentBackStackEntry ile yaşam döngüsü olaylarını dinlemenize veya akışları yaşam döngüsüne duyarlı bir şekilde toplamanıza olanak tanır.
Navigation 3'te NavDisplay, her hedefin composable içeriğine LocalLifecycleOwner.current aracılığıyla giriş kapsamlı bir LifecycleOwner sağlar. Daha fazla bilgi için Hedef yaşam döngüsü bölümünü inceleyin.
Yaşam döngüsüne duyarlı işlemleri, LocalLifecycleOwner.current öğesine referans vererek doğrudan hedefinizin composable içeriğinde gerçekleştirmeniz gerekir.
Örneğin, eski yığın girişini kullanarak yaşam döngüsüne duyarlı bir şekilde akış topluyorsanız:
Önce:
// In your destination screen or host val lifecycleOwner = navController.currentBackStackEntry!! val state by flow.collectAsStateWithLifecycle(lifecycleOwner = lifecycleOwner)
Sonra:
// Inside the destination composable val state by flow.collectAsStateWithLifecycle()
4.2. adım: Taşıma sonucunu iletme
Navigation 2'de hedefler, SavedStateHandle üzerindeki NavBackStackEntry yöntemini kullanarak sonuçları önceki hedeflere geri iletir. SavedStateHandle, kaydedilen örnek durumu tarafından desteklendiğinden döndürülen veriler işlem sonlandırmadan otomatik olarak kurtuldu.
Önce:
// Sender destination: navController.previousBackStackEntry?.savedStateHandle?.set("contact_key", contact) navController.popBackStack() // Receiver destination: val lifecycleOwner = LocalLifecycleOwner.current navController.currentBackStackEntry?.savedStateHandle ?.getLiveData<Contact>("contact_key") ?.observe(lifecycleOwner) { contact -> viewModel.onRecipientSelected(contact) }
3. gezinme bölümünde NavDisplay.entryDecorators için rememberResultEventBusNavEntryDecorator() ekleyin. Gönderen hedefinizin entryProvider eşlemesinde LocalResultEventBus.current değerini alın ve sendResult() işlevini çağırın. Alıcı hedefte, etkinliği bir ViewModel hedefine yönlendirmek veya yan etki tetiklemek için ResultEffect kullanın.
Sonra:
// Sender destination (in entryProvider): entry<ContactPickerRoute> { val resultBus = LocalResultEventBus.current ContactPickerScreen( onContactSelected = { contact -> resultBus.sendResult<Contact>(result = contact) navigator.goBack() } ) } // Receiver destination: @Composable fun ComposeMessageScreen(viewModel: ComposeMessageViewModel = viewModel()) { ResultEffect<Contact> { contact -> viewModel.onRecipientSelected(contact) } ComposeMessageContent(recipient = viewModel.recipient) }
Duruma dayalı gözlem için resultBus.conflateAsState() yöntemini çağırabilirsiniz. Daha fazla bilgi için Sonuçları döndürme bölümüne bakın.
5. adım: Hedeflerinizi NavHost adlı mülkün NavGraph adlı mülkünden bir entryProvider mülküne taşıyın
Navigation 2'de, genellikle NavHost öğesinin sondaki lambda'sında NavGraphBuilder DSL'yi kullanarak hedeflerinizi tanımlarsınız. Burada, Gezinme kodunuzu kapsülleme bölümünde açıklandığı gibi uzantı işlevlerini kullanmak yaygındır.
Navigation 3'te hedeflerinizi entryProvider kullanarak tanımlarsınız. Bu entryProvider, bir rotayı NavEntry olarak çözümler. Önemli bir nokta olarak, entryProvider, girişler arasında üst-alt ilişkileri tanımlamaz.
Bu geçiş rehberinde, alt-üst ilişkileri aşağıdaki gibi modellenmiştir:
NavigationState, bir dizi üst düzey rotaya (üst rotalar) ve her biri için bir yığına sahiptir. Mevcut üst düzey rotayı ve ilişkili yığınını takip eder.- Yeni bir rotaya giderken
Navigator, rotanın üst düzey rota olup olmadığını kontrol eder. Eşleşiyorsa mevcut üst düzey rota ve yığın güncellenir. Değilse alt rotadır ve mevcut yığına eklenir.
5.1. adım: entryProvider oluşturun
NavigationState ile aynı kapsamda DSL kullanarak bir entryProvider oluşturun.
val entryProvider = entryProvider<NavKey> { }
5.2. adım: Hedefleri entryProvider klasörüne taşıyın
NavHost içinde tanımlanan her hedef için hedef türüne göre aşağıdakileri yapın:
navigation: Rotayla birlikte silin. Üst düzey rotalar her iç içe yerleştirilmiş eski yığını tanımlayabildiğinden "temel rotalara" gerek yoktur.composable<T>: DosyayıentryProviderdizinine taşıyın ve tür parametresini koruyarakentryolarak yeniden adlandırın. Örneğin,composable<RouteA>,entry<RouteA>olur.dialog<T>:composableile aynı işlemi yapın ancak girişe şu şekilde meta veri ekleyin:entry<T>(metadata = DialogSceneStrategy.dialog()).bottomSheet: Alt sayfa tarifini buradan inceleyebilirsiniz. Bu,dialogile ilgili talimatlara benzer. Tek fark,BottomSheetSceneStrategysınıfının temel Navigation 3 kitaplığının bir parçası olmamasıdır. Bu nedenle, bu sınıfı projenize kopyalamanız gerekir.
Yapay Zeka Ajanı: İç içe yerleştirilmiş bir grafiği tanımlamak için kullanılan rotaları silerken, silinen rotaya yapılan tüm referansları iç içe yerleştirilmiş grafikteki ilk alt öğeyi tanımlamak için kullanılan türle değiştirin. Örneğin, orijinal kod navigation<BaseRouteA>{ composable<RouteA>{ ... } } ise BaseRouteA kodunu silmeniz ve bu koda yapılan tüm referansları RouteA ile değiştirmeniz gerekir. Bu değiştirme işleminin genellikle gezinme çubuğu, gezinme rayı veya gezinme çekmecesine sağlanan liste için yapılması gerekir.
NavGraphBuilder uzantı işlevlerini EntryProviderScope<T> uzantı işlevleri olarak yeniden düzenleyip taşıyabilirsiniz.
entry öğesinin sondaki lambda'sına sağlanan anahtarı kullanarak gezinme bağımsız değişkenlerini alın.
Örneğin:
// ... 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 @Composable fun NavHostSnippet(navController: NavHostController) { NavHost(navController = navController, startDestination = BaseRouteA){ composable<RouteA>{ entry -> 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() } } }
şu olur:
// ... 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. adım: NavHost yerine NavDisplay yazın
NavHost yerine NavDisplay koyun.
NavHostöğesini silipNavDisplayile değiştirin.entries = navigationState.toEntries(entryProvider)parametresini belirtin. Bu işlem, gezinme durumunuentryProviderkullanarakNavDisplaytarafından gösterilen girişlere dönüştürür.NavDisplay.onBackcihazınınavigator.goBack()cihazına bağlayın. Bu,NavDisplay'in yerleşik geri işleyicisi tamamlandığındanavigatoröğesinin gezinme durumunu güncellemesine neden olur.- İletişim kutusu hedefleriniz varsa
NavDisplay'insceneStrategiesparametresineDialogSceneStrategyekleyin.
Örneğin:
NavDisplay( entries = navigationState.toEntries(entryProvider), onBack = { navigator.goBack() }, sceneStrategies = remember { listOf(DialogSceneStrategy()) } )
7. adım: Derin bağlantıları taşıma
Navigation 2'de derin bağlantılar, hedeflerin deepLinks parametresi kullanılarak doğrudan gezinme grafiğinde tanımlanıyordu.
Navigation 3'te derin bağlantılar, gezinme kullanıcı arayüzünden bağımsız olarak yönetilir. İlk eski yığını oluşturmak için DeepLinkMatcher'leri tanımlar ve etkinliğinizdeki gelen istekleri eşleştirirsiniz.
Önce:
Navigation 2'de derin bağlantıyı şu şekilde tanımlamış olabilirsiniz:
composable<RouteA>( deepLinks = listOf( navDeepLink { uriPattern = "www.example.com/user/{id}" } ) ) { // ... }
Sonra:
3. gezinme adımında rota için bir UriDeepLinkMatcher tanımlarsınız:
val userMatcher = UriDeepLinkMatcher( DeepLinkUri("www.example.com/user/{id}"), serializer<RouteA>() )
Ardından, etkinliğinizin onCreate (ve onNewIntent) yönteminde gelen intent'i eşleştirip eski yığınınızı başlatırsınız:
val deepLinkMatchers: List<DeepLinkMatcher<*, *>> = listOf( userMatcher, ) class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val request = DeepLinkRequest(intent = intent) val matchResult = deepLinkMatchers .mapNotNull { it.match(request) } .maxOrNull() val backStack = when (matchResult) { null -> listOf(HomeKey) is BackStackMatchResult<*, *> -> { @Suppress("UNCHECKED_CAST") matchResult.backStack as List<NavKey> } else -> listOf(matchResult.key) } // Use backStack with NavDisplay } }
Özel bağımsız değişken türleri
Navigation 2'de, özel NavType uygulamaları ve typeMap kullanarak özel veya üçüncü taraf bağımsız değişken türlerini (ör. LocalDateTime) işlediniz.
Navigation 3'te, özel veya üçüncü taraf türlerini URI parametrelerinden seri durumdan çıkarmak için bir DeepLinkSerializer tanımlayın. Ayrıntılar için DeepLinkSerializer ile özel serileştirme başlıklı makaleyi inceleyin.
Sentetik eski yığınlar ve özel eşleştiriciler gibi daha ileri düzey kullanım alanları için Derin bağlantıları destekleme kılavuzuna bakın.
8. adım: Navigation 2 bağımlılıklarını kaldırın
Tüm Navigation 2 içe aktarma işlemlerini ve kitaplık bağımlılıklarını kaldırın.
Özet
Tebrikler! Projeniz Navigation 3'e taşındı. Siz veya yapay zeka temsilciniz bu kılavuzu kullanırken herhangi bir sorunla karşılaştıysanız buradan hata bildiriminde bulunun.