Oluşturma özelliğiyle gezinme

Gezinme bileşeni, Jetpack Compose uygulamaları için destek sağlar. Gezinme bileşeninin altyapısından ve özelliklerinden yararlanırken composable'lar arasında gezinebilirsiniz.

Kurulum

Compose'u desteklemek için uygulama modülünüzün build.gradle dosyasında aşağıdaki bağımlılığı kullanın:

Modern

dependencies {
    def nav_version = "2.7.7"

    implementation "androidx.navigation:navigation-compose:$nav_version"
}

Kotlin

dependencies {
    val nav_version = "2.7.7"

    implementation("androidx.navigation:navigation-compose:$nav_version")
}

Başlayın

Bir uygulamada gezinmeyi uygularken gezinme ana makinesi, grafik ve denetleyici uygulayın. Daha fazla bilgi için Gezinmeye genel bakış bölümünü inceleyin.

Compose'da NavController oluşturma hakkında bilgi edinmek için Gezinme denetleyicisi oluşturma başlıklı makalenin Oluşturma bölümüne bakın.

NavHost oluşturma

Compose'da NavHost oluşturma hakkında bilgi için Gezinme grafiğinizi tasarlama başlıklı makalenin Oluşturma bölümüne bakın.

Bir Composable'a gitme hakkında bilgi için mimari belgelerindeki Bir hedefe gitme bölümüne bakın.

Gezinme Oluşturma, composable hedefler arasında bağımsız değişkenlerin iletilmesini de destekler. Bunu yapmak için, temel gezinme kitaplığını kullanırken derin bağlantıya bağımsız değişkenler ekleme işlemine benzer şekilde, yönlendirmenize bağımsız değişken yer tutucuları eklemeniz gerekir:

NavHost(startDestination = "profile/{userId}") {
    ...
    composable("profile/{userId}") {...}
}

Varsayılan olarak, tüm bağımsız değişkenler dize olarak ayrıştırılır. composable() öğesinin arguments parametresi, NamedNavArgument nesnelerinin listesini kabul eder. navArgument() yöntemini kullanarak hızlı bir şekilde NamedNavArgument oluşturabilir ve ardından tam type değerini belirtebilirsiniz:

NavHost(startDestination = "profile/{userId}") {
    ...
    composable(
        "profile/{userId}",
        arguments = listOf(navArgument("userId") { type = NavType.StringType })
    ) {...}
}

composable() işlevinin lambdasında bulunan NavBackStackEntry bağımsız değişkenlerini ayıklamanız gerekir.

composable("profile/{userId}") { backStackEntry ->
    Profile(navController, backStackEntry.arguments?.getString("userId"))
}

Bağımsız değişkeni hedefe iletmek için navigate çağrısı yaparken URL'yi rotaya eklemeniz gerekir:

navController.navigate("profile/user1234")

Desteklenen türlerin listesi için Hedefler arasında veri aktarma bölümünü inceleyin.

Navigasyon sırasında karmaşık verileri alma

Gezinirken karmaşık veri nesnelerini aktarmamanız, bunun yerine gezinme işlemlerini gerçekleştirirken bağımsız tanımlayıcı veya başka bir kimlik biçimi gibi gerekli minimum bilgileri iletmeniz önemle tavsiye edilir:

// Pass only the user ID when navigating to a new destination as argument
navController.navigate("profile/user1234")

Karmaşık nesneler, veri katmanı gibi tek bir doğruluk kaynağında veri olarak depolanmalıdır. Rotayı izledikten sonra hedefinize ulaştığınızda, iletilen kimliği kullanarak gerekli bilgileri tek doğru kaynaktan yükleyebilirsiniz. ViewModel içinde veri katmanına erişmekten sorumlu bağımsız değişkenleri almak için ViewModel öğesinin SavedStateHandle öğesini kullanın:

class UserViewModel(
    savedStateHandle: SavedStateHandle,
    private val userInfoRepository: UserInfoRepository
) : ViewModel() {

    private val userId: String = checkNotNull(savedStateHandle["userId"])

    // Fetch the relevant user information from the data layer,
    // ie. userInfoRepository, based on the passed userId argument
    private val userInfo: Flow<UserInfo> = userInfoRepository.getUserInfo(userId)

// …

}

Bu yaklaşım, yapılandırma değişiklikleri sırasında veri kaybının ve söz konusu nesne güncellenirken ya da dönüştürüldüğünde yaşanabilecek tutarsızlıkların önlenmesine yardımcı olur.

Karmaşık verileri bağımsız değişken olarak iletmekten neden kaçınmanız gerektiği hakkında daha ayrıntılı bir açıklama ve desteklenen bağımsız değişken türlerinin listesi için Verileri hedefler arasında iletme bölümünü inceleyin.

İsteğe bağlı bağımsız değişkenler ekleyin

Gezinme Oluşturma, isteğe bağlı gezinme bağımsız değişkenlerini de destekler. İsteğe bağlı bağımsız değişkenler, gerekli bağımsız değişkenlerden iki şekilde ayrılır:

  • Sorgu parametresi söz dizimi ("?argName={argName}") kullanılarak eklenmelidir
  • Bunların bir defaultValue öğesi ayarlanmış veya nullable = true (varsayılan değeri dolaylı olarak null değerine ayarlar) içermesi gerekir.

Bu, isteğe bağlı tüm bağımsız değişkenlerin, composable() işlevine liste olarak açıkça eklenmesi gerektiği anlamına gelir:

composable(
    "profile?userId={userId}",
    arguments = listOf(navArgument("userId") { defaultValue = "user1234" })
) { backStackEntry ->
    Profile(navController, backStackEntry.arguments?.getString("userId"))
}

Artık hedefe hiçbir bağımsız değişken aktarılmasa bile bunun yerine defaultValue ("user1234") kullanılır.

Rotalar aracılığıyla bağımsız değişkenleri ele alma yapısı, composable'larınızın Navigasyon'dan tamamen bağımsız olduğu ve çok daha test edilebilir hale geldiği anlamına gelir.

Gezinme Oluşturma, composable() işlevinin bir parçası olarak tanımlanabilecek örtülü derin bağlantıları da destekler. deepLinks parametresi, navDeepLink() yöntemiyle hızlıca oluşturulabilecek NavDeepLink nesnelerinin listesini kabul eder:

val uri = "https://www.example.com"

composable(
    "profile?id={id}",
    deepLinks = listOf(navDeepLink { uriPattern = "$uri/{id}" })
) { backStackEntry ->
    Profile(navController, backStackEntry.arguments?.getString("id"))
}

Bu derin bağlantılar, belirli bir URL'yi, işlemi veya MIME türünü bir composable ile ilişkilendirmenize olanak tanır. Varsayılan olarak, bu derin bağlantılar harici uygulamalara gösterilmez. Bu derin bağlantıları harici olarak kullanılabilir hale getirmek için uygulamanızın manifest.xml dosyasına uygun <intent-filter> öğelerini eklemeniz gerekir. Önceki örnekte derin bağlantıyı etkinleştirmek için manifest'in <activity> öğesinin içine aşağıdakileri eklemeniz gerekir:

<activity …>
  <intent-filter>
    ...
    <data android:scheme="https" android:host="www.example.com" />
  </intent-filter>
</activity>

Gezinme, derin bağlantı başka bir uygulama tarafından tetiklendiğinde otomatik olarak söz konusu composable'a yönlendiren derin bağlantılardır.

Aynı derin bağlantılar, bir composable'dan uygun derin bağlantıyla PendingIntent oluşturmak için de kullanılabilir:

val id = "exampleId"
val context = LocalContext.current
val deepLinkIntent = Intent(
    Intent.ACTION_VIEW,
    "https://www.example.com/$id".toUri(),
    context,
    MyActivity::class.java
)

val deepLinkPendingIntent: PendingIntent? = TaskStackBuilder.create(context).run {
    addNextIntentWithParentStack(deepLinkIntent)
    getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
}

Daha sonra bu deepLinkPendingIntent, uygulamanızı derin bağlantı hedefinde açmak için diğer PendingIntent gibi kullanabilirsiniz.

İç İçe Navigasyon

İç içe gezinme grafikleri oluşturma hakkında bilgi edinmek için İç içe yerleştirilmiş grafikler bölümüne bakın.

Alttaki gezinme çubuğuyla entegrasyon

NavController öğesini composable hiyerarşinizde daha üst bir düzeyde tanımlayarak Navigasyon'u alt gezinme bileşeni gibi diğer bileşenlere bağlayabilirsiniz. Bunu yaptığınızda, alt çubuktaki simgeleri seçerek gezinebilirsiniz.

BottomNavigation ve BottomNavigationItem bileşenlerini kullanmak için Android uygulamanıza androidx.compose.material bağımlılığını ekleyin.

Modern

dependencies {
    implementation "androidx.compose.material:material:1.6.8"
}

android {
    buildFeatures {
        compose true
    }

    composeOptions {
        kotlinCompilerExtensionVersion = "1.5.14"
    }

    kotlinOptions {
        jvmTarget = "1.8"
    }
}

Kotlin

dependencies {
    implementation("androidx.compose.material:material:1.6.8")
}

android {
    buildFeatures {
        compose = true
    }

    composeOptions {
        kotlinCompilerExtensionVersion = "1.5.14"
    }

    kotlinOptions {
        jvmTarget = "1.8"
    }
}

Alt gezinme çubuğundaki öğeleri gezinme grafiğinizdeki rotalara bağlamak için burada görünen Screen gibi, hedeflerin rotasını ve dize kaynak kimliğini içeren mühürlü bir sınıf tanımlamanız önerilir.

sealed class Screen(val route: String, @StringRes val resourceId: Int) {
    object Profile : Screen("profile", R.string.profile)
    object FriendsList : Screen("friendslist", R.string.friends_list)
}

Ardından bu öğeleri, BottomNavigationItem tarafından kullanılabilecek bir listeye yerleştirin:

val items = listOf(
   Screen.Profile,
   Screen.FriendsList,
)

BottomNavigation composable'ınızda currentBackStackEntryAsState() işlevini kullanarak geçerli NavBackStackEntry değerini alın. Bu giriş, mevcut NavDestination öğesine erişmenizi sağlar. Daha sonra her bir BottomNavigationItem öğesinin seçili durumu, öğenin rotası ile mevcut hedefin ve üst hedeflerinin rotası karşılaştırılarak, NavDestination hiyerarşisini kullanarak iç içe yerleştirilmiş gezinme kullandığınız durumların ele alınmasıyla belirlenebilir.

Öğenin rotası, onClick lambda'yı navigate çağrısına bağlamak için de kullanılır. Böylece öğeye dokunduğunuzda ilgili öğeye yönlendirilirsiniz. saveState ve restoreState işaretleri kullanıldığında, bu öğenin durumu ve geri yığını doğru şekilde kaydedilir ve siz alttaki gezinme öğeleri arasında geçiş yaparken geri yüklenir.

val navController = rememberNavController()
Scaffold(
  bottomBar = {
    BottomNavigation {
      val navBackStackEntry by navController.currentBackStackEntryAsState()
      val currentDestination = navBackStackEntry?.destination
      items.forEach { screen ->
        BottomNavigationItem(
          icon = { Icon(Icons.Filled.Favorite, contentDescription = null) },
          label = { Text(stringResource(screen.resourceId)) },
          selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
          onClick = {
            navController.navigate(screen.route) {
              // Pop up to the start destination of the graph to
              // avoid building up a large stack of destinations
              // on the back stack as users select items
              popUpTo(navController.graph.findStartDestination().id) {
                saveState = true
              }
              // Avoid multiple copies of the same destination when
              // reselecting the same item
              launchSingleTop = true
              // Restore state when reselecting a previously selected item
              restoreState = true
            }
          }
        )
      }
    }
  }
) { innerPadding ->
  NavHost(navController, startDestination = Screen.Profile.route, Modifier.padding(innerPadding)) {
    composable(Screen.Profile.route) { Profile(navController) }
    composable(Screen.FriendsList.route) { FriendsList(navController) }
  }
}

Burada navController durumunu NavHost işlevinden kaldırmak ve BottomNavigation bileşeniyle paylaşmak için NavController.currentBackStackEntryAsState() yönteminden yararlanacaksınız. Bu, BottomNavigation öğesinin otomatik olarak en güncel duruma sahip olduğu anlamına gelir.

Yazmada Gezinme'de güvenlik yazın

Bu sayfadaki kod tür için güvenli değil. navigate() işlevini var olmayan rotalar veya yanlış bağımsız değişkenlerle çağırabilirsiniz. Bununla birlikte, gezinme kodunuzu çalışma zamanında tür açısından güvenli olacak şekilde yapılandırabilirsiniz. Bu sayede kilitlenmeleri önleyebilir ve şunlardan emin olabilirsiniz:

  • Bir hedef veya gezinme grafiğine giderken sağladığınız bağımsız değişkenler doğru türdedir ve gerekli tüm bağımsız değişkenlerin mevcut olması gerekir.
  • SavedStateHandle öğesinden aldığınız bağımsız değişkenler doğru türdedir.

Bu hakkında daha fazla bilgi için Kotlin DSL ve Gezinme Oluşturma Aracı'nda tür güvenliği bölümüne bakın.

Birlikte çalışabilirlik

Oluştur ile Gezinme bileşenini kullanmak istiyorsanız iki seçeneğiniz vardır:

  • Parçalar için Gezinme bileşeniyle bir gezinme grafiği tanımlayın.
  • Oluştur'da Oluştur hedeflerini kullanarak NavHost ile bir gezinme grafiği tanımlayın. Bu yalnızca gezinme grafiğindeki tüm ekranlar composable ise mümkündür.

Bu nedenle, karma Oluşturma ve Görünümler uygulamaları için önerimiz Parça tabanlı Gezinme bileşenini kullanmaktır. Parçalar; Görüntüleme tabanlı ekranları, Oluşturma ekranlarını ve hem Görünümler hem de Oluştur'u kullanan ekranları tutar. Her bir Parça'nın içeriği Oluşturma'ya geçtikten sonraki adım, tüm bu ekranları Gezinme Oluşturma özelliği ile birbirine bağlamak ve tüm Parçaları kaldırmaktır.

Compose kodu içindeki hedefleri değiştirmek için hiyerarşideki herhangi bir composable'a iletilebilen ve tetiklenebilen etkinlikleri kullanıma sunarsınız.

@Composable
fun MyScreen(onNavigate: (Int) -> Unit) {
    Button(onClick = { onNavigate(R.id.nav_profile) } { /* ... */ }
}

Parçanızda, NavController öğesini bulup hedefe giderek Oluşturma ile parça tabanlı Gezinme bileşeni arasında bir köprü oluşturursunuz:

override fun onCreateView( /* ... */ ) {
    setContent {
        MyScreen(onNavigate = { dest -> findNavController().navigate(dest) })
    }
}

Alternatif olarak NavController öğesini Compose hiyerarşinizde aşağı aktarabilirsiniz. Ancak basit işlevlerin gösterilmesi çok daha fazla yeniden kullanılabilir ve test edilebilir.

Test etme

Gezinme kodunu composable hedeflerinizden ayırarak her composable'ı NavHost composable'dan ayrı olarak test edebilirsiniz.

Bu, navController öğesini doğrudan herhangi bir composable öğesine iletmemeniz ve bunun yerine gezinme geri çağırmalarını parametre olarak iletmeniz gerektiği anlamına gelir. Bu, testlerde navController örneği gerektirmediğinden tüm composable'larınızın ayrı ayrı test edilebilir.

composable lambda'sının sağladığı dolaylı yönlendirme düzeyi, Gezinme kodunuzu composable'dan ayırmanızı sağlar. Bu yöntem iki yönde çalışır:

  • composable'ınıza yalnızca ayrıştırılan bağımsız değişkenleri iletin
  • Gezinmek için NavController yerine composable tarafından tetiklenmesi gereken lambda'ları iletin.

Örneğin, giriş olarak userId alan ve kullanıcıların bir arkadaşının profil sayfasına gitmesine izin veren Profile composable'ı şu imzaya sahip olabilir:

@Composable
fun Profile(
    userId: String,
    navigateToFriendProfile: (friendUserId: String) -> Unit
) {
 …
}

Bu şekilde, Profile composable, Navigasyon'dan bağımsız çalışarak bağımsız olarak test edilebilir. composable lambda'sı, Gezinme API'leri ile composable'ınız arasındaki boşluğu kapatmak için gereken minimum mantığı içerir.

composable(
    "profile?userId={userId}",
    arguments = listOf(navArgument("userId") { defaultValue = "user1234" })
) { backStackEntry ->
    Profile(backStackEntry.arguments?.getString("userId")) { friendUserId ->
        navController.navigate("profile?userId=$friendUserId")
    }
}

NavHost, composable'larınıza iletilen gezinme işlemleri ve bağımsız ekran composable'larınızı test ederek uygulamada gezinmeyle ilgili ihtiyaçlarınızı karşılayacak testler yazmanız önerilir.

NavHost test ediliyor

NavHost öğenizi test etmeye başlamak için aşağıdaki gezinme testi bağımlılığını ekleyin:

dependencies {
// ...
  androidTestImplementation "androidx.navigation:navigation-testing:$navigationVersion"
  // ...
}

NavHost test konunuzu oluşturabilir ve navController örneğinin bir örneğini aktarabilirsiniz. Gezinme testi yapısı bunun için bir TestNavHostController sağlar. Uygulamanızın ve NavHost'nin başlangıç hedefini doğrulayan kullanıcı arayüzü testi aşağıdaki gibi görünür:

class NavigationTest {

    @get:Rule
    val composeTestRule = createComposeRule()
    lateinit var navController: TestNavHostController

    @Before
    fun setupAppNavHost() {
        composeTestRule.setContent {
            navController = TestNavHostController(LocalContext.current)
            navController.navigatorProvider.addNavigator(ComposeNavigator())
            AppNavHost(navController = navController)
        }
    }

    // Unit test
    @Test
    fun appNavHost_verifyStartDestination() {
        composeTestRule
            .onNodeWithContentDescription("Start Screen")
            .assertIsDisplayed()
    }
}

Gezinme işlemlerini test etme

Kullanıcı arayüzü öğelerine tıklayarak ve ardından görüntülenen hedefi doğrulayarak veya beklenen rotayı mevcut rotayla karşılaştırarak navigasyon uygulamanızı çeşitli şekillerde test edebilirsiniz.

Beton uygulamanızın uygulamasını test etmek istediğiniz için kullanıcı arayüzüne tıklama tercih edilir. Bunu bağımsız composable işlevleriyle birlikte ayrı ayrı test etmeyi öğrenmek için Jetpack Compose'da test etme codelab'ine göz atmayı unutmayın.

Ayrıca, navController currentBackStackEntry ile mevcut dize rotasını beklenen rotayla karşılaştırarak onaylarınızı kontrol etmek için navController kullanabilirsiniz:

@Test
fun appNavHost_clickAllProfiles_navigateToProfiles() {
    composeTestRule.onNodeWithContentDescription("All Profiles")
        .performScrollTo()
        .performClick()

    val route = navController.currentBackStackEntry?.destination?.route
    assertEquals(route, "profiles")
}

Compose testinin temelleri hakkında daha fazla yardım için Compose düzeninizi test etme ve Jetpack Compose'da test etme codelab'ine göz atın. Gezinme kodunu ileri düzey test etme hakkında daha fazla bilgi edinmek için Gezinme Testi kılavuzunu ziyaret edin.

Daha fazla bilgi

Jetpack Navigasyon hakkında daha fazla bilgi edinmek için Gezinme bileşenini kullanmaya başlama bölümüne bakın veya Jetpack Compose Navigasyon codelab'e katılın.

Uygulamanızın gezinme şeklini farklı ekran boyutlarına, yönlere ve form faktörlerine uyum sağlayacak şekilde nasıl tasarlayacağınızı öğrenmek için Duyarlı kullanıcı arayüzleri için gezinme bölümüne bakın.

Modüler hale getirilmiş bir uygulamada, iç içe yerleştirilmiş grafikler ve alt gezinme çubuğu entegrasyonu gibi kavramlar dahil olmak üzere daha gelişmiş bir Compose gezinme uygulaması hakkında bilgi edinmek için GitHub'daki Now in Android uygulamasına göz atın.

Numuneler