Oluşturma özelliğiyle gezinme

Gezinme bileşeni, Jetpack Composer uygulamaları için destek sağlar. Gezinme bileşeninin altyapısından ve özelliklerinden faydalanı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 bir gezinme ana makinesi, grafik ve denetleyici uygulayın. Daha fazla bilgi için Navigasyon'a genel bakış başlıklı makaleye göz atın.

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

NavHost Oluşturma

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

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

Gezinme Yazma, composable hedefler arasında bağımsız değişkenlerin aktarılmasını da destekler. Bunu yapmak için yönlendirmenize, temel gezinme kitaplığını kullanırken derin bağlantıya bağımsız değişkenler eklemeye benzer şekilde 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 şeklinde ayrıştırılır. composable() öğesinin arguments parametresi, NamedNavArgument nesnelerinden oluşan bir listeyi kabul eder. navArgument() yöntemini kullanarak hızlıca bir 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 })
    ) {...}
}

Bağımsız değişkenleri, composable() işlevinin lambdasında bulunan NavBackStackEntry öğesinden 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ı yaptığınızda, bağımsız değişkeni rotaya eklemeniz gerekir:

navController.navigate("profile/user1234")

Desteklenen türlerin listesi için Hedefler arasında veri iletme bölümüne bakın.

Gezinirken karmaşık verileri alma

Gezinme sırasında karmaşık veri nesnelerini iletmemeniz, bunun yerine, gezinme işlemlerini gerçekleştirirken bağımsız tanımlayıcı veya başka bir kimlik biçimi gibi gerekli minimum bilgilerin iletilmesi önerilir:

// 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ğru kaynakta veri olarak depolanmalıdır. Navigasyonu tamamladıktan sonra hedefinize ulaştığınızda, iletilen kimliği kullanarak gerekli bilgileri tek ve doğru kaynaktan yükleyebilirsiniz. ViewModel üzerinde veri katmanına erişmekten sorumlu bağımsız değişkenleri almak için ViewModel öğesinin SavedStateHandle değerini 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ı ve söz konusu nesne güncellenirken veya dönüştürülürken oluşabilecek tutarsızlıkların önlenmesine yardımcı olur.

Karmaşık verileri neden bağımsız değişken olarak geçirmekten 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 Hedefler arasında veri iletme bölümüne bakın.

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

Gezinme Yazma, 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 farklıdır:

  • Bunlar, sorgu parametresi söz dizimi ("?argName={argName}") kullanılarak dahil edilmelidir
  • Bunlarda defaultValue veya nullable = true değerleri bulunmalıdır (varsayılan değer dolaylı olarak null olarak ayarlanır)

Bu, isteğe bağlı tüm bağımsız değişkenlerin composable() işlevine açık bir şekilde liste olarak 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 iletilmese bile defaultValue "user1234" kullanılır.

Bağımsız değişkenlerin rotalar aracılığıyla ele alınma yapısı, composable'larınızın Navigasyon'dan tamamen bağımsız olarak kalmasını ve çok daha test edilebilir olmasını sağlar.

Gezinme Yazma, composable() işlevinin bir parçası olarak tanımlanabilen örtülü derin bağlantıları da destekler. deepLinks parametresi, navDeepLink() yöntemi kullanılarak hızlı bir şekilde oluşturulabilen NavDeepLink nesnelerinin bir 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. Bu derin bağlantılar, varsayılan olarak 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. Yukarıdaki örnekte derin bağlantıyı etkinleştirmek için manifestin <activity> öğesi içinde aşağıdakileri eklemeniz gerekir:

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

Navigasyon, derin bağlantı başka bir uygulama tarafından tetiklendiğinde otomatik olarak ilgili composable'a derin bağlantı oluşturur.

Aynı derin bağlantılar, bir composable'dan uygun derin bağlantıyla bir 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 öğesini, derin bağlantı hedefinde uygulamanızı açmak için diğer herhangi bir PendingIntent gibi kullanabilirsiniz.

İç İçe Navigasyon

İç içe yerleştirilmiş gezinme grafiklerinin nasıl oluşturulacağı hakkında bilgi için İç içe yerleştirilmiş grafikler konusuna bakın.

Alt gezinme çubuğuyla entegrasyon

Oluşturulabilir hiyerarşinizde daha yüksek bir düzeyde NavController tanımlayarak Gezinme'yi alt gezinme bileşeni gibi diğer bileşenlere bağlayabilirsiniz. Bu sayede 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.4"
}

android {
    buildFeatures {
        compose true
    }

    composeOptions {
        kotlinCompilerExtensionVersion = "1.5.11"
    }

    kotlinOptions {
        jvmTarget = "1.8"
    }
}

Kotlin

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

android {
    buildFeatures {
        compose = true
    }

    composeOptions {
        kotlinCompilerExtensionVersion = "1.5.11"
    }

    kotlinOptions {
        jvmTarget = "1.8"
    }
}

Alttaki gezinme çubuğundaki öğeleri gezinme grafiğinizdeki rotalara bağlamak için hedefler için rotayı ve Dize kaynak kimliğini içeren Screen gibi kapalı 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 mevcut NavBackStackEntry değerini alın. Bu giriş, geçerli NavDestination öğesine erişmenizi sağlar. Ardından her bir BottomNavigationItem öğesinin seçilen durumu, NavDestination hiyerarşisini kullanarak iç içe yerleştirilmiş gezinme kullandığınızda öğenin rotası, geçerli hedefin ve üst hedeflerinin rotasıyla karşılaştırılarak belirlenebilir.

Öğenin rotası, onClick lambda'yı navigate öğesine yapılan bir çağrıya bağlamak için de kullanılır. Böylece öğeye dokunduğunuzda söz konusu öğeye yönlendirilirsiniz. saveState ve restoreState işaretlerini kullandığınızda, söz konusu öğenin durum ve arka yığını doğru şekilde kaydedilir ve alt 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 yararlanıyorsunuz. Yani BottomNavigation otomatik olarak en güncel duruma sahip olur.

Gezinme Yazma bölümünde yazma güvenliği

Bu sayfadaki kod tür açısından güvenli değil. navigate() işlevini mevcut olmayan rotalarla veya yanlış bağımsız değişkenlerle çağırabilirsiniz. Ancak, çalışma zamanında Gezinme kodunuzu yazma güvenlikli olacak şekilde yapılandırabilirsiniz. Böylece kilitlenmeleri önleyebilir ve aşağıdakileri yapabilirsiniz:

  • Bir hedefe veya gezinme grafiğine giderken sağladığınız bağımsız değişkenler doğru türlerdir ve gerekli tüm bağımsız değişkenler mevcuttur.
  • SavedStateHandle işlevinden aldığınız bağımsız değişkenler doğru türlerdir.

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

Birlikte çalışabilirlik

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

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

Bu nedenle, karma Oluşturma ve Görüntüleme uygulamaları için öneri, Parça Tabanlı Gezinme bileşenini kullanmaktır. Daha sonra parçalar, Görüntüleme tabanlı ekranları, Oluştur ekranlarını ve hem Görüntüleme hem de Oluşturma'yı kullanan ekranları barındırır. Her Parçanın içeriği Oluştur'a aktarıldıktan sonraki adım, tüm bu ekranları Gezinme Oluşturma Aracı ile birleştirmek ve tüm Parçaları kaldırmaktır.

Oluşturma kodu içindeki hedefleri değiştirmek için hiyerarşideki herhangi bir composable'a iletilebilecek ve tetikleyebilecek etkinlikleri ortaya koyarsınız:

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

Parçanızda, NavController öğesini bulup hedefe giderek Compose ile parçaya dayalı Gezinme bileşeni arasındaki köprüyü oluşturursunuz:

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

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

Test etme

Her composable'ı NavHost composable'dan ayrı olarak ayrı ayrı test edebilmek için gezinme kodunu composable hedeflerinizden ayırın.

Bu, navController öğesini doğrudan herhangi bir composable'a 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 tek tek test edilebilir.

composable lambda'nın sağladığı dolaylı yol, Gezinme kodunuzu composable'dan ayırmanıza olanak sağlar. Bu iki şekilde çalışır:

  • composable dosyanıza yalnızca ayrıştırılan bağımsız değişkenleri iletin.
  • NavController öğesinin kendisi yerine, gezinmek için composable tarafından tetiklenmesi gereken lambda'ları iletin.

Örneğin, userId değerini girdi olarak alan ve kullanıcıların bir arkadaşlarının profil sayfasına gitmesine olanak tanıyan bir Profile composable'ı şu imzaya sahip olabilir:

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

Bu sayede Profile composable, Navigasyon'dan bağımsız çalışarak bağımsız olarak test edilebilir. composable lambda, Navigation API'leri ile composable'ınız arasındaki boşluğu doldurmak 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şlemlerini ve ayrı ayrı ekran composable'larınızı test ederek uygulamanızda gezinme gereksinimlerinizi karşılayan 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 bu konuya iletebilirsiniz. Bunun için gezinme test yapısı bir TestNavHostController sağlar. Uygulamanızın ve NavHost adlı uygulamanın başlangıç hedefini doğrulayan bir kullanıcı arayüzü testi şu şekilde 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

Gezinme uygulamanızı, 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 çeşitli şekillerde test edebilirsiniz.

Somut uygulamanızın kullanımını test etmek istediğiniz için kullanıcı arayüzüne tıklamalar tercih edilir. Bunu bağımsız composable işlevleriyle birlikte tek başına nasıl test edeceğinizi öğrenmek için Jetpack Compose'da Test Yapma codelab'ine göz atmayı unutmayın.

Mevcut Dize rotasını beklenen rotayla karşılaştırarak onaylarınızı kontrol etmek için navController API'sini (navController) currentBackStackEntry kullanarak da navController kullanabilirsiniz:

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

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

Compose testiyle ilgili temel bilgiler hakkında daha fazla yardım için Compose düzeninizi test etme ve Jetpack Compose'da test etme codelab'ine bakın. Gezinme kodunun gelişmiş test edilmesi 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 Navigasyon bileşenini kullanmaya başlama başlıklı makaleye bakın veya Jetpack Compose navigasyon codelab'ine katılın.

Uygulamanızın gezinme öğelerini 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.

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

Numuneler