Google berkomitmen untuk mendorong terwujudnya keadilan ras bagi komunitas Kulit Hitam. Lihat caranya.

Menavigasi dengan Compose

Komponen Navigasi memberikan dukungan untuk aplikasi Jetpack Compose. Anda dapat menavigasi di antara komponen sekaligus memanfaatkan infrastruktur dan fitur komponen Navigasi.

Penyiapan

Untuk mendukung Compose, gunakan dependensi berikut dalam file build.gradle modul aplikasi Anda:

dependencies {
    def nav_compose_version = "1.0.0-alpha01"
    implementation "androidx.navigation:navigation-compose:$nav_compose_version"
}

Memulai

NavController adalah API pusat untuk komponen Navigasi. API ini bersifat stateful serta memantau data sebelumnya pada komponen yang membentuk layar di aplikasi Anda dan status setiap layar.

Anda dapat membuat NavController dengan menggunakan metode rememberNavController() dalam komponen Anda:

val navController = rememberNavController()

Anda harus membuat NavController di tempat dalam hierarki komponen, yakni tempat semua komponen yang perlu mereferensikannya dapat mengaksesnya. Hal ini mengikuti prinsip penarikan status dan memungkinkan Anda menggunakan NavController dan status yang diberikannya melalui currentBackStackEntryAsState() untuk digunakan sebagai sumber kebenaran demi memperbarui komponen di luar layar. Lihat Integrasi dengan menu navigasi bawah untuk contoh fungsi ini.

Membuat NavHost

Setiap NavController harus diatribusikan dengan satu komponen NavHost. NavHost menautkan NavController dengan grafik navigasi yang menentukan beberapa tujuan komponen yang seharusnya dapat Anda jelajahi di antaranya. Saat Anda menavigasi di antara komponen, konten NavHost akan otomatis direkomposisi. Setiap tujuan komponen dalam grafik navigasi diatribusikan dengan rute.

Membuat NavHost memerlukan NavController yang telah dibuat sebelumnya melalui rememberNavController() dan rute tujuan awal grafik Anda. Pembuatan NavHost menggunakan sintaksis lambda dari Navigation Kotlin DSL untuk membuat grafik navigasi Anda. Anda dapat menambahkan ke struktur navigasi dengan menggunakan metode composable(). Metode ini mengharuskan Anda memberikan rute dan komponen yang harus ditautkan ke tujuan:

NavHost(navController, startDestination = "profile") {
    composable("profile") { Profile(...) }
    composable("friendslist") { FriendsList(...) }
    ...
}

Untuk menavigasi ke tujuan komponen di grafik navigasi, Anda harus menggunakan metode navigate(). navigate() mengambil parameter String tunggal yang mewakili rute tujuan. Untuk menavigasi dari komponen dalam grafik navigasi, panggil navigate():

fun Profile(navController: NavController) {
    ...
    Button(onClick = { navController.navigate("friends") }) {
        Text(text = "Navigate next")
    }
    ...
}

Anda hanya boleh memanggil navigate() sebagai bagian dari callback, bukan dari komponen itu sendiri, guna mencegah pemanggilan navigate() pada setiap rekomposisi.

Navigation Compose juga mendukung penerusan argumen di antara tujuan komponen. Untuk melakukannya, Anda perlu menambahkan placeholder argumen ke rute, mirip dengan cara Anda menambahkan argumen ke deep link saat menggunakan library navigasi dasar:

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

Secara default, semua argumen diuraikan sebagai string. Anda dapat menentukan jenis lain dengan menggunakan parameter arguments untuk menetapkan type:

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

Anda harus mengekstrak NavArguments dari NavBackStackEntry yang tersedia di lambda fungsi composable().

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

Untuk meneruskan argumen ke tujuan, Anda perlu menambahkan nilai ke rute sebagai ganti placeholder dalam panggilan ke navigate:

navController.navigate("profile/user1234")

Untuk daftar jenis yang didukung, lihat Meneruskan data di antara tujuan.

Menambahkan argumen opsional

Navigation Compose juga mendukung argumen navigasi opsional. Argumen opsional memiliki dua perbedaan dengan argumen yang diwajibkan:

  • Argumen ini harus disertakan menggunakan sintaksis parameter kueri ("?argName={argName}")
  • Argumen harus memiliki defaultValue yang ditetapkan, atau nullability = true (yang secara implisit menetapkan nilai default ke null)

Ini berarti bahwa semua argumen opsional harus ditambahkan secara eksplisit ke fungsi composable() sebagai daftar:

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

Sekarang, meskipun tidak ada argumen yang diteruskan ke tujuan, defaultValue "saya" akan digunakan sebagai gantinya.

Struktur penanganan argumen melalui rute berarti bahwa komponen Anda tetap sepenuhnya independen dari Navigasi dan jauh lebih dapat diuji.

Navigation Compose mendukung deep link implisit yang juga dapat ditentukan sebagai bagian dari fungsi composable(). Anda dapat menambahkannya sebagai daftar menggunakan navDeepLink():

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

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

Deep link ini memungkinkan Anda mengatribusikan URL, tindakan, dan/atau jenis mime tertentu dengan komponen. Secara default, deep link ini tidak diekspos ke aplikasi eksternal. Untuk membuat deep link ini tersedia secara eksternal, Anda harus menambahkan elemen <intent-filter> yang sesuai ke file manifest.xml aplikasi. Untuk mengaktifkan deep link di atas, Anda harus menambahkan yang berikut di dalam elemen <activity> dari manifes:

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

Navigasi akan otomatis membuat deep link ke dalam komponen tersebut saat deep link dipicu oleh aplikasi lain.

Deep link yang sama ini juga dapat digunakan untuk mem-build PendingIntent dengan deep link yang sesuai dari komponen:

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

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

Anda selanjutnya dapat menggunakan deepLinkPendingIntent ini seperti PendingIntent lainnya untuk membuka aplikasi di tujuan deep link.

Integrasi dengan menu navigasi bawah

Dengan menentukan NavController pada level yang lebih tinggi dalam hierarki komponen, Anda dapat menghubungkan Navigasi dengan komponen lain seperti BottomNavBar. Dengan melakukan hal ini, Anda dapat menavigasi dengan memilih ikon di menu bawah.

Untuk menautkan item di menu navigasi bawah ke rute di grafik navigasi, sebaiknya tetapkan class tertutup, seperti Screen yang terlihat di sini, yang berisi rute dan ID resource string untuk tujuan.

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

Lalu, tempatkan item tersebut dalam daftar yang dapat digunakan oleh BottomNavigationItem:

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

Pada komponen BottomNavigation, dapatkan NavBackStackEntry menggunakan fungsi currentBackStackEntryAsState(), dan dengan menggunakan entri tersebut, ambil rute dari argumen menggunakan konstanta KEY_ROUTE yang merupakan bagian dari NavHostController. Dengan rute tersebut, tentukan apakah item yang dipilih adalah tujuan saat ini dan berikan respons secara tepat dengan menyetel label, menyoroti item, dan menavigasi jika rute tersebut tidak cocok.

val navController = rememberNavController()
Scaffold(
    bottomBar = {
        BottomNavigation {
            val navBackStackEntry by navController.currentBackStackEntryAsState()
            val currentRoute = navBackStackEntry?.arguments?.getString(KEY_ROUTE)
            items.forEach { screen ->
                BottomNavigationItem(
                    icon = { Icon(Icons.Filled.Favorite) },
                    label = { Text(stringResource(screen.resourceId)) },
                    selected = currentRoute == screen.route,
                    onClick = {
                        // This is the equivalent to popUpTo the start destination
                        navController.popBackStack(navController.graph.startDestination, false)

                        // This if check gives us a "singleTop" behavior where we do not create a
                        // second instance of the composable if we are already on that destination
                        if (currentRoute != screen.route) {
                            navController.navigate(screen.route)
                        }
                    }
                )
            }
        }
    }
) {

    NavHost(navController, startDestination = Screen.Profile.route) {
        composable(Screen.Profile.route) { Profile(navController) }
        composable(Screen.FriendsList.route) { FriendsList(navController) }
    }
}

Di sini, Anda memanfaatkan metode NavController.currentBackStackEntryAsState() untuk menarik status navController dari fungsi NavHost, dan membagikannya dengan komponen BottomNavigation. Ini berarti BottomNavigation secara otomatis memiliki status terbaru.

Pengujian

Kami sangat merekomendasikan agar Anda memisahkan kode Navigasi dari tujuan komponen untuk memungkinkan pengujian setiap komponen secara terpisah dari komponen NavHost.

Level pengalihan yang disediakan oleh lambda composable memungkinkan Anda memisahkan kode Navigasi dari komponen itu sendiri. Hal ini bekerja dalam dua arah:

  • Hanya meneruskan argumen yang diuraikan ke dalam komponen
  • Meneruskan lambda yang seharusnya dipicu oleh komponen untuk menavigasi, bukan NavController itu sendiri.

Misalnya, komponen Profile yang mengambil userId sebagai input dan memungkinkan pengguna menavigasi ke halaman profil teman mungkin memiliki tanda tangan:


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

Di sini, kita melihat bahwa komponen Profile berfungsi secara independen dari Navigasi, sehingga membuatnya dapat diuji secara terpisah. Lambda composable akan mengenkapsulasi logika minimal yang diperlukan untuk menjembatani kesenjangan antara Navigation API dan komponen Anda:

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

Pelajari lebih lanjut

Untuk mempelajari Navigasi Jetpack lebih lanjut, lihat Memulai komponen Navigasi.