Komponen Navigasi menyediakan bahasa khusus domain, atau DSL, berbasis Kotlin yang bergantung pada type-safe builder Kotlin.
API ini memungkinkan Anda membuat grafik secara deklaratif di kode Kotlin, bukan di dalam resource XML. Cara ini dapat berguna jika Anda ingin membuat navigasi aplikasi secara dinamis. Misalnya, aplikasi Anda dapat mendownload dan meng-cache konfigurasi navigasi dari layanan web eksternal, lalu menggunakan konfigurasi tersebut untuk membuat grafik navigasi secara dinamis dalam fungsi onCreate()
aktivitas Anda.
Dependensi
Untuk menggunakan DSL Kotlin, tambahkan dependensi berikut ke file build.gradle
aplikasi Anda:
Groovy
dependencies { def nav_version = "2.7.7" api "androidx.navigation:navigation-fragment-ktx:$nav_version" }
Kotlin
dependencies { val nav_version = "2.7.7" api("androidx.navigation:navigation-fragment-ktx:$nav_version") }
Membuat grafik
Mari kita mulai dengan contoh sederhana berdasarkan aplikasi Sunflower. Untuk contoh ini, kita memiliki dua tujuan: home
dan plant_detail
. Tujuan home
ada saat pengguna pertama kali meluncurkan aplikasi. Tujuan ini menampilkan daftar tanaman dari taman pengguna. Saat pengguna memilih salah satu tanaman, aplikasi akan menavigasi ke tujuan plant_detail
.
Gambar 1 menunjukkan tujuan berikut beserta argumen yang diperlukan oleh tujuan plant_detail
dan tindakan to_plant_detail
yang digunakan aplikasi untuk menavigasi dari home
ke plant_detail
.
Menghosting Grafik Nav DSL Kotlin
Agar dapat membuat grafik navigasi aplikasi, Anda memerlukan tempat untuk menghosting
grafik tersebut. Contoh ini menggunakan fragmen sehingga menghosting grafik dalam
NavHostFragment
di dalam
FragmentContainerView
:
<!-- activity_garden.xml -->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true" />
</FrameLayout>
Perhatikan bahwa atribut app:navGraph
tidak ditetapkan dalam contoh ini. Grafik
tidak ditentukan sebagai
resource di
folder res/navigation
sehingga perlu ditetapkan sebagai bagian dari proses
onCreate()
dalam aktivitas.
Dalam XML, sebuah tindakan menggabungkan ID tujuan dengan satu atau beberapa argumen. Namun, saat menggunakan DSL Navigasi, rute dapat berisi argumen sebagai bagian dari rute. Artinya, tidak ada konsep tindakan saat menggunakan DSL.
Langkah berikutnya adalah menentukan beberapa konstanta yang akan Anda gunakan saat menentukan grafik Anda.
Membuat konstanta untuk grafik
Grafik navigasi berbasis XML
diuraikan sebagai bagian dari proses build Android. Konstanta numerik dibuat
untuk setiap atribut id
yang ditentukan dalam grafik. ID statis yang dihasilkan waktu build ini
tidak tersedia ketika membuat grafik navigasi saat runtime, sehingga
DSL Navigasi menggunakan string rute, bukan ID. Setiap rute direpresentasikan oleh
string unik, dan sebaiknya tentukan rute tersebut sebagai konstanta untuk mengurangi
risiko bug terkait kesalahan ketik.
Saat menangani argumen, argumen tersebut dibangun ke dalam string rute. Menerapkan logika ini ke dalam rute dapat, sekali lagi, mengurangi risiko bug terkait kesalahan ketik yang dialami.
object nav_routes {
const val home = "home"
const val plant_detail = "plant_detail"
}
object nav_arguments {
const val plant_id = "plant_id"
const val plant_name = "plant_name"
}
Membuat grafik dengan NavGraphBuilder DSL
Setelah menentukan konstanta, Anda dapat membuat grafik navigasi.
val navController = findNavController(R.id.nav_host_fragment)
navController.graph = navController.createGraph(
startDestination = nav_routes.home
) {
fragment<HomeFragment>(nav_routes.home) {
label = resources.getString(R.string.home_title)
}
fragment<PlantDetailFragment>("${nav_routes.plant_detail}/{${nav_arguments.plant_id}}") {
label = resources.getString(R.string.plant_detail_title)
argument(nav_arguments.plant_id) {
type = NavType.StringType
}
}
}
Dalam contoh ini, lambda di akhir mendefinisikan dua tujuan fragmen menggunakan fungsi builder DSL fragment()
. Fungsi ini memerlukan string rute untuk tujuan
yang diperoleh dari konstanta. Fungsi tersebut juga menerima lambda
opsional untuk konfigurasi tambahan, seperti label tujuan, serta
fungsi builder tersemat untuk argumen dan deep link.
Class Fragment
yang mengelola UI setiap tujuan diteruskan sebagai jenis yang diparameterisasi dalam tanda kurung sudut (<>
). Ini memiliki efek yang sama seperti menetapkan atribut android:name
pada tujuan fragmen yang ditentukan menggunakan XML.
Menavigasi dengan grafik DSL Kotlin
Terakhir, Anda dapat bernavigasi dari home
ke plant_detail
menggunakan panggilan
NavController.navigation()
standar:
private fun navigateToPlant(plantId: String) {
findNavController().navigate("${nav_routes.plant_detail}/$plantId")
}
Di PlantDetailFragment
, Anda dapat memperoleh nilai argumen seperti yang ditunjukkan
pada contoh berikut:
val plantId: String? = arguments?.getString(nav_arguments.plant_id)
Detail tentang cara menyediakan argumen saat menavigasi dapat ditemukan di bagian memberikan argumen tujuan.
Bagian lainnya dalam panduan ini menjelaskan elemen grafik navigasi umum, tujuan, dan cara menggunakannya saat membuat grafik.
Tujuan
DSL Kotlin menyediakan dukungan bawaan untuk tiga jenis tujuan:
tujuan Fragment
, Activity
, dan NavGraph
, yang masing-masing memiliki
fungsi ekstensi inline yang tersedia untuk mem-build dan mengonfigurasi
tujuan.
Tujuan fragmen
Fungsi DSL
fragment()
dapat diparameterisasi untuk class fragmen penerapan dan memerlukan
string rute unik untuk ditetapkan ke tujuan ini, diikuti dengan lambda tempat
Anda dapat memberikan konfigurasi tambahan seperti dijelaskan dalam bagian
Menavigasi dengan grafik DSL Kotlin.
fragment<FragmentDestination>(nav_routes.route_name) {
label = getString(R.string.fragment_title)
// arguments, deepLinks
}
Tujuan aktivitas
Fungsi DSL activity()
memerlukan string rute unik untuk ditetapkan ke tujuan ini, tetapi
tidak diparameterisasi ke class aktivitas penerapan apa pun. Sebaliknya, Anda akan menetapkan
activityClass
opsional pada lambda di akhir. Fleksibilitas ini memungkinkan Anda
menentukan tujuan aktivitas untuk aktivitas yang harus diluncurkan menggunakan
intent implisit, jika
class aktivitas eksplisit tidak memungkinkan. Seperti tujuan fragmen,
Anda juga dapat mengonfigurasi label, argumen, dan deep link.
activity(nav_routes.route_name) {
label = getString(R.string.activity_title)
// arguments, deepLinks...
activityClass = ActivityDestination::class
}
Tujuan grafik navigasi
Fungsi DSL navigation()
dapat digunakan untuk membuat
grafik navigasi bertingkat.
Fungsi ini memerlukan tiga argumen: rute untuk
ditetapkan ke grafik, rute tujuan awal grafik, dan
lambda untuk mengonfigurasi grafik lebih lanjut. Elemen yang valid mencakup tujuan,
argumen, dan deep link lainnya, serta
label deskriptif untuk tujuan.
Label ini dapat berguna untuk mengikat grafik navigasi ke komponen
UI menggunakan
NavigationUI
navigation("route_to_this_graph", nav_routes.home) {
// label, other destinations, deep links
}
Mendukung tujuan kustom
Jika Anda menggunakan
jenis tujuan baru yang tidak langsung
mendukung DSL Kotlin, Anda dapat menambahkan tujuan ini ke DSL
Kotlin menggunakanaddDestination()
:
// The NavigatorProvider is retrieved from the NavController
val customDestination = navigatorProvider[CustomNavigator::class].createDestination().apply {
route = Graph.CustomDestination.route
}
addDestination(customDestination)
Sebagai alternatif, Anda juga dapat menggunakan operator unary plus untuk menambahkan tujuan yang baru dibuat langsung ke grafik:
// The NavigatorProvider is retrieved from the NavController
+navigatorProvider[CustomNavigator::class].createDestination().apply {
route = Graph.CustomDestination.route
}
Menyediakan argumen tujuan
Tujuan mana pun dapat menentukan argumen yang bersifat opsional atau wajib. Tindakan
dapat ditentukan menggunakan
fungsi argument()
pada NavDestinationBuilder
, yang merupakan class dasar untuk semua
jenis builder tujuan. Fungsi ini menggunakan nama argumen sebagai string
dan lambda yang digunakan untuk membuat dan mengonfigurasi
NavArgument
.
Di dalam lambda, Anda dapat menentukan jenis data argumen, nilai default jika berlaku, dan apakah nullable atau tidak.
fragment<PlantDetailFragment>("${nav_routes.plant_detail}/{${nav_arguments.plant_id}}") {
label = getString(R.string.plant_details_title)
argument(nav_arguments.plant_id) {
type = NavType.StringType
defaultValue = getString(R.string.default_plant_id)
nullable = true // default false
}
}
Jika defaultValue
diberikan, jenisnya dapat disimpulkan. Jika defaultValue
dan type
diberikan, jenisnya harus cocok. Baca
dokumentasi referensi NavType untuk
mengetahui daftar lengkap jenis argumen yang tersedia.
Menyediakan jenis kustom
Jenis tertentu, seperti
ParcelableType
dan
SerializableType
,
tidak mendukung penguraian nilai dari string yang digunakan berdasarkan rute atau deep link.
Hal ini karena jenis tersebut tidak bergantung pada refleksi saat runtime. Dengan menyediakan class
NavType
kustom, Anda dapat mengontrol dengan tepat cara jenis Anda diuraikan dari rute atau
deep link. Hal ini memungkinkan Anda menggunakan
Serialisasi Kotlin atau library
lainnya untuk menyediakan encoding dan decoding jenis kustom Anda tanpa refleksi.
Misalnya, class data yang merepresentasikan parameter penelusuran yang diteruskan ke
layar penelusuran Anda dapat mengimplementasikan Serializable
(untuk memberikan
dukungan encoding/decoding) dan Parcelize
(untuk mendukung penyimpanan ke dan pemulihan
dari Bundle
):
@Serializable
@Parcelize
data class SearchParameters(
val searchQuery: String,
val filters: List<String>
)
NavType
kustom dapat ditulis sebagai:
val SearchParametersType = object : NavType<SearchParameters>(
isNullableAllowed = false
) {
override fun put(bundle: Bundle, key: String, value: SearchParameters) {
bundle.putParcelable(key, value)
}
override fun get(bundle: Bundle, key: String): SearchParameters {
return bundle.getParcelable(key) as SearchParameters
}
override fun serializeAsValue(value: SearchParameters): String {
// Serialized values must always be Uri encoded
return Uri.encode(Json.encodeToString(value))
}
override fun parseValue(value: String): SearchParameters {
// Navigation takes care of decoding the string
// before passing it to parseValue()
return Json.decodeFromString<SearchParameters>(value)
}
}
Jenis ini kemudian dapat digunakan di DSL Kotlin Anda seperti jenis lainnya:
fragment<SearchFragment>(nav_routes.plant_search) {
label = getString(R.string.plant_search_title)
argument(nav_arguments.search_parameters) {
type = SearchParametersType
defaultValue = SearchParameters("cactus", emptyList())
}
}
NavType
mengenkapsulasi penulisan dan pembacaan setiap kolom, yang berarti bahwa NavType
juga harus digunakan saat Anda menavigasi ke tujuan untuk memastikan formatnya cocok:
val params = SearchParameters("rose", listOf("available"))
val searchArgument = SearchParametersType.serializeAsValue(params)
navController.navigate("${nav_routes.plant_search}/$searchArgument")
Parameter ini dapat diperoleh dari argumen dalam tujuan:
val params: SearchParameters? = arguments?.getParcelable(nav_arguments.search_parameters)
Deep link
Deep link dapat ditambahkan ke tujuan mana pun, seperti yang dapat dilakukan dengan grafik navigasi berbasis XML. Semua prosedur yang sama seperti yang ditentukan dalam Membuat deep link untuk tujuan berlaku untuk proses pembuatan deep link eksplisit menggunakan DSL Kotlin.
Namun, saat membuat deep link implisit,
Anda tidak memiliki resource navigasi XML yang dapat dianalisis untuk
elemen <deepLink>
. Oleh karena itu, Anda tidak dapat mengandalkan penempatan elemen
<nav-graph>
di file AndroidManifest.xml
dan harus menambahkan
filter intent ke aktivitas Anda secara manual.
Filter intent yang Anda berikan harus cocok dengan pola URL dasar, tindakan, dan
mimetype deep link aplikasi Anda.
Anda dapat menyediakan deeplink
yang lebih spesifik untuk setiap tujuan deep link
secara terpisah menggunakan
fungsi DSL
deepLink()
. Fungsi ini menerima NavDeepLink
yang berisi String
yang mewakili pola URI, String
yang mewakili tindakan intent, dan
String
yang mewakili mimeType.
Contoh:
deepLink {
uriPattern = "http://www.example.com/plants/"
action = "android.intent.action.MY_ACTION"
mimeType = "image/*"
}
Tidak ada batasan jumlah deep link yang dapat Anda tambahkan. Setiap kali Anda memanggil
deepLink()
,
deep link baru akan ditambahkan ke daftar yang dikelola untuk tujuan tersebut.
Skenario deep link implisit yang lebih kompleks, yang juga menentukan parameter berbasis kueri dan jalur, ditampilkan di bawah ini:
val baseUri = "http://www.example.com/plants"
fragment<PlantDetailFragment>(nav_routes.plant_detail) {
label = getString(R.string.plant_details_title)
deepLink(navDeepLink {
uriPattern = "${baseUri}/{id}"
})
deepLink(navDeepLink {
uriPattern = "${baseUri}/{id}?name={plant_name}"
})
}
Anda dapat menggunakan jenis interpolasi string untuk menyederhanakan definisi.
Batasan
Plugin Safe Args tidak
kompatibel dengan DSL Kotlin, karena plugin tersebut mencari file resource XML untuk
menghasilkan class Directions
dan Arguments
.
Pelajari lebih lanjut
Lihat halaman Keamanan jenis navigasi guna mempelajari cara memberikan keamanan jenis untuk kode Navigation Compose dan DSL Kotlin.