Navigation 2'den Navigation 3'e geçiş yapma

Uygulamanızı Navigation 2'den Navigation 3'e taşımak için aşağıdaki adımları uygulayın:

  1. Navigation 3 bağımlılıklarını ekleyin.
  2. NavKey arayüzünü uygulamak için gezinme rotalarınızı güncelleyin.
  3. Gezinme durumunuzu tutmak ve değiştirmek için sınıflar oluşturun.
  4. NavController yerine bu sınıfları kullanın.
  5. Hedeflerinizi NavHost'nın NavGraph'sinden entryProvider'ye taşıyın.
  6. NavHost yerine NavDisplay koyun.
  7. Navigation 2 bağımlılıklarını kaldırın.

Yapay Zeka Temsilcisi kullanma

Bu kılavuzu, Android Studio'nun Agent Modu'ndaki Gemini gibi bir yapay zeka aracısıyla birlikte kullanabilirsiniz. Bu kılavuzda "AI Agent:" ile başlayan satırlar yapay zeka temsilcisi tarafından okunmalı ancak insan okuyucular tarafından yoksayılabilir.

Hazırlık

Aşağıdaki bölümlerde taşıma 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

  • 36 veya daha yeni bir compileSdk kullanmanız gerekir.
  • Navigasyon terminolojisi hakkında bilgi sahibi olmanız gerekir.
  • Hedefler, composable işlevlerdir. Navigation 3 yalnızca Compose için tasarlanmıştır. Compose'da Fragment'leri ve View'ları kullanmak için Compose'da View'ları Kullanma başlıklı makaleyi inceleyin.
  • Rotalar kesin olarak yazılır. Dize tabanlı rotalar kullanıyorsanız önce tür güvenli rotalara geçin (örnek).
  • İsteğe bağlıdır (ancak önemle tavsiye edilir): Mevcut gezinme davranışını doğrulayan test kapsamı. Bu, taşıma işlemi tamamlandıktan sonra gezinme davranışının değişmediğini doğrular.

Varsayımlar

Bu kılavuzda, sizin ve projenizle ilgili aşağıdaki varsayımlar yapılmıştır:

  • Birkaç üst düzey rotanız var (genellikle alt gezinme çubuğunda gösterilir) ve her birinin kendi geri yığını var.
  • Geri yığınlar arasında geçiş yaptığınızda yığının durumu ve tüm hedefleri korunur.
  • Uygulamadan her zaman Ana ekran üzerinden çıkarsınız. Bu ekran, uygulama başlatıldığında gösterilen ilk ekrandır.
  • Gezinme 2'den Gezinme 3'e geçişi tek bir atomik değişiklik olarak yapmak istiyorsunuz. Navigation 2 kodunun Navigation 3 koduyla birlikte kullanıldığı artımlı bir taşıma işlemi gerçekleştirmiyorsunuz.

Yapay Zeka Aracısı: Herhangi bir kodu değiştirmeden ö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:

  • Composable işlevler olarak tanımlanan hedefler
  • İletişim kutuları (bir hedefin başka bir hedefin üzerinde gösterilmesi)

Tarifler aracılığıyla desteklenen özellikler

Kod tarifleri deposunda aşağıdakiler de dahil olmak üzere daha ayrıntılı kullanım alanları gösterilmektedir:

Projenizde bu özelliklerden herhangi biri varsa nasıl uygulanacağını anlamak için ilgili tarifi inceleyin.

Yapay Zeka Aracısı: Herhangi bir kodu değiştirmeden önce projede tariflerle desteklenen özellikler olup olmadığını kontrol edin. Bu durumda, tarifin README dosyasını ve kaynak kodunu kontrol edin. Tarife göre bir taşıma planı oluşturun. Planı kullanıcıyla onaylamadan devam etmeyin.

Desteklenmeyen özellikler

Bu taşıma kılavuzu ve kod tarifleri henüz aşağıdaki özellikleri desteklemiyor. Bunları Gezinme 3'ü kullanarak uygulayamayacağınız anlamına gelmez. Sadece burada ele alınmamışlardır.

  • Birden fazla iç içe yerleştirilmiş gezinme seviyesi
  • Paylaşılan hedefler: Farklı geri yığınları arasında hareket edebilen ekranlar
  • Özel hedef türleri
  • Derin bağlantılar

Yapay Zeka Aracısı: Herhangi bir kodu değiştirmeden önce projenin desteklenmeyen özelliklerden herhangi birini içerip içermediğini kontrol edin. Bu durumda 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

Projenize Navigation 3 bağımlılıklarını eklemek için Başlayın sayfasını kullanın. Temel bağımlılıklar kopyalamanız için sağlanır.

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)
}

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 kullanabilirsiniz.

Önce:

@Serializable data object RouteA

Değişiklikten 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 tutucusu oluşturun

Aşağıdaki kodu NavigationState.kt adlı bir dosyaya kopyalayın. Paket adınızı proje yapınıza uygun şekilde 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 Temsilcisi: rememberSerializable doğru. Bu değeri rememberSaveable olarak değiştirmeyin.

Bu dosya, NavigationState adlı bir durum tutucu sınıf ve ilişkili yardımcı işlevler içerir. Her biri kendi geri yığınına sahip 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 rotanın geri yığınlarını kalıcı hale getirmek için rememberNavBackStack kullanılı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ıza uygun olacak ş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:

  • navigate belirli bir rotaya.
  • goBack rotadan ayrıldınız.

Her iki yöntem de NavigationState öğesini değiştirir.

3.3. adım: NavigationState ve Navigator öğelerini oluşturun

NavigationState ve Navigator için NavController ile aynı kapsamda örnekler 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 yerine koyun

NavController gezinme etkinliği yöntemlerini Navigator eşdeğerleriyle değiştirin.

NavController alanı veya yöntemi

Navigator eşdeğer

navigate()

navigate()

popBackStack()

goBack()

NavController alanlarını NavigationState alanlarıyla değiştirin.

NavController alanı veya yöntemi

NavigationState eşdeğer

currentBackStack

backStacks[topLevelRoute]

currentBackStackEntry

currentBackStackEntryAsState()

currentBackStackEntryFlow

currentDestination

backStacks[topLevelRoute].last()

En üst düzey rotayı alma: Bulmak için geçerli geri yığın girişinden hiyerarşide yukarı doğru ilerleyin.

topLevelRoute

Gezinme çubuğunda şu anda 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

Değişiklikten Sonra:

val isSelected = key == navigationState.topLevelRoute

İçe aktarılanlar da dahil olmak üzere NavController ile ilgili tüm referansları kaldırdığınızı doğrulayın.

5. adım: Hedeflerinizi NavHost'nın NavGraph alanından entryProvider alanına taşıyın

Navigation 2'de, hedeflerinizi tanımlamak için genellikle NavHost'nin sondaki lambda'sının içinde NavGraphBuilder DSL'yi kullanırsınız. Encapsulate your navigation code (Gezinme kodunuzu kapsülleme) bölümünde açıklandığı gibi, burada uzantı işlevlerinin kullanılması yaygındır.

Navigation 3'te, hedeflerinizi entryProvider kullanarak tanımlarsınız. Bu entryProvider, rotayı NavEntry olarak çözümler. Önemli olarak, entryProvider, girişler arasındaki üst-alt ilişkilerini tanımlamaz.

Bu taşıma kılavuzunda, alt-üst ilişkileri aşağıdaki gibi modellenir:

  • 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 bir rota olup olmadığını kontrol eder. Bu durumda, mevcut üst düzey rota ve yığın güncellenir. Aksi takdirde, bu bir alt rota olur ve mevcut yığına eklenir.

5.1. adım: entryProvider oluşturun

entryProvider DSL'yi kullanarak, NavigationState ile aynı kapsamda bir entryProvider oluşturun.

val entryProvider = entryProvider {

}

5.2. adım: Hedefleri entryProvider içine 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 silinir. Üst düzey rotalar her bir iç içe yerleştirilmiş geri yığını tanımlayabildiğinden "base routes" gerekmez.
  • composable<T>: entryProvider klasörüne taşıyın ve tür parametresini koruyarak entry olarak yeniden adlandırın. Örneğin, composable<RouteA> dönüştürülüp entry<RouteA> haline getirilir.
  • dialog<T>: composable ile aynı işlemi yapın ancak girişe meta verileri aşağıdaki gibi ekleyin: entry<T>(metadata = DialogSceneStrategy.dialog()).
  • bottomSheet: Buradaki alt sayfa tarifini uygulayın. Bu, dialog ile ilgili talimatlara benzer. Ancak BottomSheetSceneStrategy, temel Navigation 3 kitaplığının bir parçası değildir. Bu nedenle, bunu projenize kopyalamanız gerekir.

Yapay Zeka Aracısı: İç 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şlemi genellikle bir gezinme çubuğuna, rayına veya çekmecesine sağlanan liste için yapılmalıdır.

NavGraphBuilder uzantı işlevlerini EntryProviderScope<T> uzantı işlevlerine yeniden düzenleyip taşıyabilirsiniz.

entry'nın 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

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() }
    }
}

ş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 ifadesini NavDisplay ile değiştirin.

NavHost yerine NavDisplay koyun.

  • NavHost öğesini silip NavDisplay ile değiştirin.
  • entries = navigationState.toEntries(entryProvider) değerini parametre olarak belirtin. Bu, gezinme durumunu entryProvider kullanarak NavDisplay'nın gösterdiği girişlere dönüştürür.
  • NavDisplay.onBacknavigator.goBack()'e bağlayın. Bu, NavDisplay'nin yerleşik geri işleyicisi tamamlandığında navigator'nın gezinme durumunu güncellemesine neden olur.
  • Diyalog hedefleriniz varsa DialogSceneStrategy parametresine NavDisplay ekleyin.sceneStrategy

Örneğin:

import androidx.navigation3.ui.NavDisplay

NavDisplay(
    entries = navigationState.toEntries(entryProvider),
    onBack = { navigator.goBack() },
    sceneStrategy = remember { DialogSceneStrategy() }
)

7. 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 artık Navigation 3'e taşındı. Bu kılavuzu kullanırken siz veya yapay zeka aracınız herhangi bir sorunla karşılaştıysa buradan hata bildirin.