Komponen Navigasi memberikan dukungan untuk aplikasi Jetpack Compose. Anda dapat bernavigasi antar-composable sekaligus memanfaatkan infrastruktur dan fitur komponen Navigasi.
Penyiapan
Untuk mendukung Compose, gunakan dependensi berikut dalam file
build.gradle
modul aplikasi Anda:
Groovy
dependencies { def nav_version = "2.8.4" implementation "androidx.navigation:navigation-compose:$nav_version" }
Kotlin
dependencies { val nav_version = "2.8.4" implementation("androidx.navigation:navigation-compose:$nav_version") }
Memulai
Saat menerapkan navigasi di aplikasi, terapkan host, grafik, dan pengontrol navigasi. Untuk informasi selengkapnya, lihat ringkasan Navigasi.
Membuat NavController
Untuk informasi tentang cara membuat NavController
di Compose, lihat bagian Compose
di Membuat pengontrol navigasi.
Membuat NavHost
Untuk mengetahui informasi tentang cara membuat NavHost
di Compose, lihat bagian Compose
dalam Mendesain grafik navigasi.
Menavigasi ke komponen
Untuk informasi tentang cara menavigasi ke Composable, lihat Menavigasi ke tujuan dalam dokumentasi arsitektur.
Menavigasi dengan argumen
Untuk informasi tentang cara meneruskan argumen antar-tujuan composable, lihat bagian Compose di Mendesain grafik navigasi.
Mengambil data kompleks saat menavigasi
Sangat disarankan untuk tidak meneruskan objek data yang kompleks saat menavigasi, tetapi sebagai gantinya teruskan informasi minimum yang diperlukan, seperti ID unik atau bentuk ID lainnya, sebagai argumen saat melakukan tindakan navigasi:
// Pass only the user ID when navigating to a new destination as argument
navController.navigate(Profile(id = "user1234"))
Objek yang kompleks harus disimpan sebagai data dalam satu sumber tepercaya, seperti lapisan data. Begitu Anda tiba di tujuan setelah menavigasi, Anda dapat
memuat informasi yang diperlukan dari satu sumber tepercaya dengan menggunakan
ID yang diteruskan. Untuk mengambil argumen di ViewModel
yang bertanggung jawab
mengakses lapisan data, gunakan SavedStateHandle
ViewModel
:
class UserViewModel(
savedStateHandle: SavedStateHandle,
private val userInfoRepository: UserInfoRepository
) : ViewModel() {
private val profile = savedStateHandle.toRoute<Profile>()
// Fetch the relevant user information from the data layer,
// ie. userInfoRepository, based on the passed userId argument
private val userInfo: Flow<UserInfo> = userInfoRepository.getUserInfo(profile.id)
// …
}
Pendekatan ini membantu mencegah kehilangan data selama perubahan konfigurasi dan inkonsistensi saat objek yang dimaksud sedang diperbarui atau berubah.
Untuk penjelasan yang lebih mendalam tentang alasan Anda harus menghindari penerusan data kompleks sebagai argumen, serta daftar jenis argumen yang didukung, lihat Meneruskan data antar-tujuan.
Deep link
Navigation Compose juga mendukung deep link yang dapat ditentukan sebagai bagian dari
fungsi composable()
. Parameter deepLinks
menerima daftar
objek NavDeepLink
yang dapat dibuat dengan cepat menggunakan
metode navDeepLink()
:
@Serializable data class Profile(val id: String)
val uri = "https://www.example.com"
composable<Profile>(
deepLinks = listOf(
navDeepLink<Profile>(basePath = "$uri/profile")
)
) { backStackEntry ->
ProfileScreen(id = backStackEntry.toRoute<Profile>().id)
}
Deep link ini memungkinkan Anda mengatribusikan URL, tindakan, atau jenis mime tertentu dengan
composable. 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 dalam contoh sebelumnya, 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 otomatis membuat deep link ke dalam composable 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 = "exampleId"
val context = LocalContext.current
val deepLinkIntent = Intent(
Intent.ACTION_VIEW,
"https://www.example.com/profile/$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.
Navigasi Bertingkat
Untuk informasi tentang cara membuat grafik navigasi bertingkat, lihat Grafik bertingkat.
Integrasi dengan menu navigasi bawah
Dengan menentukan NavController
pada level yang lebih tinggi dalam hierarki composable,
Anda dapat menghubungkan Navigasi dengan komponen lain seperti komponen navigasi
bawah. Dengan melakukan hal ini, Anda dapat menavigasi dengan memilih ikon di panel
bawah.
Untuk menggunakan komponen BottomNavigation
dan BottomNavigationItem
,
tambahkan dependensi androidx.compose.material
ke aplikasi Android Anda.
Groovy
dependencies { implementation "androidx.compose.material:material:1.7.5" } android { buildFeatures { compose true } composeOptions { kotlinCompilerExtensionVersion = "1.5.15" } kotlinOptions { jvmTarget = "1.8" } }
Kotlin
dependencies { implementation("androidx.compose.material:material:1.7.5") } android { buildFeatures { compose = true } composeOptions { kotlinCompilerExtensionVersion = "1.5.15" } kotlinOptions { jvmTarget = "1.8" } }
Untuk menautkan item di menu navigasi bawah ke rute di grafik navigasi,
sebaiknya tetapkan class, seperti TopLevelRoute
yang terlihat di sini, yang memiliki
class rute dan ikon.
data class TopLevelRoute<T : Any>(val name: String, val route: T, val icon: ImageVector)
Kemudian, tempatkan rute tersebut dalam daftar yang dapat digunakan oleh
BottomNavigationItem
:
val topLevelRoutes = listOf(
TopLevelRoute("Profile", Profile, Icons.Profile),
TopLevelRoute("Friends", Friends, Icons.Friends)
)
Pada composable BottomNavigation
, dapatkan NavBackStackEntry
saat ini
menggunakan fungsi currentBackStackEntryAsState()
. Entri ini memberi Anda akses
ke NavDestination
saat ini. Status yang dipilih dari setiap
BottomNavigationItem
selanjutnya dapat ditentukan dengan membandingkan rute item dengan
rute tujuan saat ini dan tujuan induknya untuk menangani kasus
saat Anda menggunakan navigasi bertingkat menggunakan hierarki
NavDestination
.
Rute item juga digunakan untuk menghubungkan lambda onClick
ke panggilan ke
navigate
agar mengetuk item akan membuka item tersebut. Dengan menggunakan
tanda saveState
dan restoreState
, status dan data sebelumnya dari item tersebut
disimpan dan dipulihkan dengan benar saat Anda beralih di antara item navigasi
bawah.
val navController = rememberNavController()
Scaffold(
bottomBar = {
BottomNavigation {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
topLevelRoutes.forEach { topLevelRoute ->
BottomNavigationItem(
icon = { Icon(topLevelRoute.icon, contentDescription = topLevelRoute.name) },
label = { Text(topLevelRoute.name) },
selected = currentDestination?.hierarchy?.any { it.hasRoute(topLevelRoute.route::class) } == true,
onClick = {
navController.navigate(topLevelRoute.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 = Profile, Modifier.padding(innerPadding)) {
composable<Profile> { ProfileScreen(...) }
composable<Friends> { FriendsScreen(...) }
}
}
Di sini, Anda memanfaatkan metode NavController.currentBackStackEntryAsState()
untuk
mengangkat status navController
dari fungsi NavHost
, dan
membagikannya ke komponen BottomNavigation
. Ini berarti
BottomNavigation
otomatis memiliki status terbaru.
Interoperabilitas
Jika ingin menggunakan komponen Navigasi dengan Compose, Anda memiliki dua opsi:
- Menentukan grafik navigasi dengan komponen Navigasi untuk fragmen.
- Menentukan grafik navigasi dengan
NavHost
di Compose menggunakan tujuan Compose. Hal ini hanya dapat dilakukan jika semua layar di grafik navigasi berupa composable.
Oleh karena itu, rekomendasi untuk aplikasi Compose dan View campuran adalah menggunakan komponen Navigasi berbasis Fragment. Fragment kemudian akan menyimpan layar berbasis View, layar Compose, dan layar yang menggunakan View dan Compose. Setelah setiap konten Fragment berada di Compose, langkah berikutnya adalah mengikat semua layar tersebut dengan Navigation Compose dan menghapus semua Fragment.
Menavigasi dari Compose dengan Navigasi untuk fragmen
Untuk mengubah tujuan di dalam kode Compose, Anda menampilkan peristiwa yang dapat diteruskan ke dan dipicu oleh setiap komponen dalam hierarki:
@Composable
fun MyScreen(onNavigate: (Int) -> Unit) {
Button(onClick = { onNavigate(R.id.nav_profile) } { /* ... */ }
}
Dalam fragmen, Anda membuat bridge antara Compose dan komponen Navigasi
berbasis fragmen dengan menemukan NavController
dan menavigasi ke
tujuan:
override fun onCreateView( /* ... */ ) {
setContent {
MyScreen(onNavigate = { dest -> findNavController().navigate(dest) })
}
}
Atau, Anda dapat meneruskan NavController
ke hierarki Compose.
Namun, menampilkan fungsi sederhana jauh lebih dapat digunakan kembali dan dapat diuji.
Pengujian
Pisahkan kode navigasi dari tujuan composable untuk memungkinkan pengujian
setiap composable secara terpisah, terpisah dari composable NavHost
.
Ini berarti Anda tidak boleh meneruskan navController
langsung ke
composable mana pun, tetapi meneruskan callback navigasi sebagai parameter. Hal ini memungkinkan
semua composable Anda dapat diuji satu per satu, karena tidak memerlukan
instance navController
dalam pengujian.
Level pengalihan tidak langsung yang disediakan oleh lambda composable
memungkinkan Anda
memisahkan kode Navigasi dari composable itu sendiri. Hal ini bekerja dalam
dua arah:
- Hanya meneruskan argumen yang diuraikan ke dalam komponen
- Meneruskan lambda yang seharusnya dipicu oleh composable untuk menavigasi, bukan
NavController
itu sendiri.
Misalnya, composable ProfileScreen
yang mengambil userId
sebagai input dan
memungkinkan pengguna menavigasi ke halaman profil teman mungkin memiliki tanda tangan:
@Composable
fun ProfileScreen(
userId: String,
navigateToFriendProfile: (friendUserId: String) -> Unit
) {
…
}
Dengan cara ini, composable ProfileScreen
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:
@Serializable data class Profile(id: String)
composable<Profile> { backStackEntry ->
val profile = backStackEntry.toRoute<Profile>()
ProfileScreen(userId = profile.id) { friendUserId ->
navController.navigate(route = Profile(id = friendUserId))
}
}
Sebaiknya tulis pengujian yang mencakup persyaratan navigasi aplikasi Anda dengan
menguji NavHost
, tindakan navigasi yang diteruskan ke composable serta
setiap composable layar.
Menguji NavHost
Untuk mulai menguji NavHost
, tambahkan dependensi pengujian navigasi
berikut:
dependencies {
// ...
androidTestImplementation "androidx.navigation:navigation-testing:$navigationVersion"
// ...
}
Gabungkan NavHost
aplikasi Anda dalam composable yang menerima NavHostController
sebagai
parameter.
@Composable
fun AppNavHost(navController: NavHostController){
NavHost(navController = navController){ ... }
}
Sekarang Anda dapat menguji AppNavHost
dan semua logika navigasi yang ditentukan di dalam
NavHost
dengan meneruskan instance artefak pengujian navigasi
TestNavHostController
. Pengujian UI yang memverifikasi tujuan awal
aplikasi Anda dan NavHost
akan terlihat seperti ini:
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()
}
}
Menguji tindakan navigasi
Anda dapat menguji implementasi navigasi dalam beberapa cara dengan melakukan klik pada elemen UI, lalu memverifikasi tujuan yang ditampilkan atau dengan membandingkan rute yang diharapkan dengan rute saat ini.
Karena Anda ingin menguji penerapan aplikasi konkret, sebaiknya klik UI. Untuk mempelajari cara mengujinya beserta setiap fungsi composable individualnya, pastikan Anda melihat codelab Pengujian di Jetpack Compose.
Anda juga dapat menggunakan navController
untuk memeriksa pernyataan dengan membandingkan
rute saat ini dengan yang diharapkan, menggunakan
currentBackStackEntry
navController
:
@Test
fun appNavHost_clickAllProfiles_navigateToProfiles() {
composeTestRule.onNodeWithContentDescription("All Profiles")
.performScrollTo()
.performClick()
assertTrue(navController.currentBackStackEntry?.destination?.hasRoute<Profile>() ?: false)
}
Untuk panduan selengkapnya tentang dasar-dasar pengujian Compose, lihat Menguji tata letak Compose dan codelab Pengujian di Jetpack Compose. Untuk mempelajari lebih lanjut pengujian lanjutan kode navigasi, kunjungi panduan Navigasi Pengujian.
Pelajari lebih lanjut
Untuk mempelajari Navigasi Jetpack lebih lanjut, lihat Memulai komponen Navigasi atau ikuti codelab Navigasi Jetpack Compose.
Untuk mempelajari cara mendesain navigasi aplikasi agar dapat beradaptasi dengan berbagai ukuran, orientasi, dan faktor bentuk layar, lihat Navigasi untuk UI responsif.
Untuk mempelajari implementasi navigasi Compose lanjutan di aplikasi modular, termasuk konsep seperti grafik bertingkat dan integrasi menu navigasi bawah, lihat aplikasi Now in Android di GitHub.
Contoh
Direkomendasikan untuk Anda
- Catatan: teks link ditampilkan saat JavaScript nonaktif
- Desain Material 2 di Compose
- Memigrasikan Jetpack Navigation ke Navigation Compose
- Tempat mengangkat status