Fragmen dan DSL Kotlin

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 dengan Fragment, tambahkan dependensi berikut ke atribut File build.gradle:

Groovy

dependencies {
    def nav_version = "2.9.2"

    api "androidx.navigation:navigation-fragment-ktx:$nav_version"
}

Kotlin

dependencies {
    val nav_version = "2.9.2"

    api("androidx.navigation:navigation-fragment-ktx:$nav_version")
}

Membuat grafik

Berikut adalah contoh dasar berdasarkan class Sunflower aplikasi. Untuk ini misalnya, 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.

Aplikasi Sunflower memiliki dua tujuan bersama dengan tindakan yang menghubungkannya.
Gambar 1. Aplikasi Sunflower memiliki dua tujuan, home dan plant_detail, bersama dengan tindakan yang menghubungkan keduanya.

Menghosting Grafik Nav DSL Kotlin

Agar dapat membuat grafik navigasi aplikasi, Anda memerlukan tempat untuk menghosting grafik. 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 rute yang akan Anda gunakan saat menentukan grafik.

Membuat rute 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 saat membuat grafik navigasi saat runtime, sehingga Navigation DSL menggunakan jenis yang dapat diserialisasi, bukan ID. Setiap rute diwakili oleh jenis yang unik.

Saat menangani argumen, argumen ini dibangun ke dalam rute jenis data. Hal ini memungkinkan Anda memiliki keamanan jenis untuk argumen navigasi.

@Serializable data object Home
@Serializable data class Plant(val id: String)

Setelah menentukan rute, Anda dapat membuat grafik navigasi.

val navController = findNavController(R.id.nav_host_fragment)
navController.graph = navController.createGraph(
    startDestination = Home
) {
    fragment<HomeFragment, Home> {
        label = resources.getString(R.string.home_title)
    }
    fragment<PlantDetailFragment, PlantDetail> {
        label = resources.getString(R.string.plant_detail_title)
    }
}

Dalam contoh ini, dua tujuan fragmen didefinisikan menggunakan fragment() Fungsi pembangun DSL. Fungsi ini memerlukan dua argumen jenis .

Pertama, class Fragment yang menyediakan UI untuk tujuan ini. Menetapkan ini memiliki efek yang sama seperti menetapkan atribut android:name pada tujuan fragmen yang ditentukan menggunakan XML.

Kedua, rute. Ini harus berupa jenis serializable yang diperluas dari Any. Ini harus berisi argumen navigasi apa pun yang akan digunakan oleh tujuan ini, dan jenisnya.

Fungsi tersebut juga menerima lambda opsional untuk konfigurasi tambahan, seperti sebagai label tujuan, serta fungsi builder tersemat untuk argumen dan deep link.

Terakhir, Anda dapat bernavigasi dari home ke plant_detail menggunakan panggilan NavController.navigate():

private fun navigateToPlant(plantId: String) {
   findNavController().navigate(route = PlantDetail(id = plantId))
}

Di PlantDetailFragment, Anda bisa mendapatkan argumen navigasi dengan mendapatkan NavBackStackEntry saat ini dan memanggil toRoute di atasnya untuk mendapatkan instance rute.

val plantDetailRoute = findNavController().getBackStackEntry<PlantDetail>().toRoute<PlantDetail>()
val plantId = plantDetailRoute.id

Jika PlantDetailFragment menggunakan ViewModel, dapatkan instance rute menggunakan SavedStateHandle.toRoute

val plantDetailRoute = savedStateHandle.toRoute<PlantDetail>()
val plantId = plantDetailRoute.id

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

Tujuan fragment() Fungsi DSL dapat diparameterisasi dengan class fragmen untuk UI dan jenis rute yang digunakan untuk mengidentifikasi tujuan ini secara unik, diikuti oleh lambda tempat Anda dapat memberikan konfigurasi tambahan sebagaimana dijelaskan dalam Menavigasi dengan grafik DSL Kotlin Anda.

fragment<MyFragment, MyRoute> {
   label = getString(R.string.fragment_title)
   // custom argument types, deepLinks
}

Tujuan aktivitas

Fungsi DSL activity() memerlukan parameter jenis untuk rute, tetapi tidak diparameterisasi ke class aktivitas penerapan apa pun. Sebagai gantinya, Anda menyetel activityClass opsional di lambda 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 kustom, dan deep link.

activity<MyRoute> {
   label = getString(R.string.activity_title)
   // custom argument types, deepLinks...

   activityClass = MyActivity::class
}

Fungsi DSL navigation() dapat digunakan untuk membuat grafik navigasi bertingkat. Fungsi ini mengambil jenis untuk rute yang akan ditetapkan ke grafik ini. Fungsi ini juga memerlukan dua argumen: rute tujuan awal grafik, dan lambda untuk lebih lanjut mengonfigurasi grafik. Elemen yang valid mencakup tujuan lainnya, argumen kustom jenis, deep link, dan label deskriptif untuk tujuan. Label ini berguna untuk mengikat grafik navigasi ke komponen UI menggunakan NavigationUI

@Serializable data object HomeGraph
@Serializable data object Home

navigation<HomeGraph>(startDestination = 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 menggunakan addDestination():

// 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

Argumen tujuan dapat ditetapkan sebagai bagian dari class rute. Dapat berupa didefinisikan dengan cara yang sama seperti yang Anda lakukan untuk class Kotlin apa pun. Argumen wajib didefinisikan sebagai jenis non-nullable dan argumen opsional ditentukan dengan nilai default.

Mekanisme pokok untuk merepresentasikan rute dan argumennya adalah {i>string<i} berbasis browser. Menggunakan string untuk memodelkan rute memungkinkan status navigasi disimpan dan dipulihkan dari disk selama perubahan konfigurasi dan penghentian proses yang dimulai sistem. Karena alasan ini, setiap argumen navigasi harus dapat diserialisasi, yaitu, argumen tersebut harus memiliki yang mengonversi representasi nilai argumen dalam memori menjadi String.

Plugin serialisasi Kotlin otomatis menghasilkan metode serialisasi untuk jenis dasar saat anotasi @Serializable ditambahkan ke objek.

@Serializable
data class MyRoute(
  val id: String,
  val myList: List<Int>,
  val optionalArg: String? = null
)

fragment<MyFragment, MyRoute>

Menyediakan jenis kustom

Untuk jenis argumen kustom, Anda harus menyediakan class NavType kustom. Hal ini memungkinkan Anda mengontrol dengan tepat cara jenis Anda diuraikan dari rute atau deep link.

Misalnya, rute yang digunakan untuk mendefinisikan layar penelusuran bisa berisi kelas yang mewakili parameter penelusuran:

@Serializable
data class SearchRoute(val parameters: SearchParameters)

@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, SearchRoute>(
    typeMap = mapOf(typeOf<SearchParameters>() to SearchParametersType)
) {
    label = getString(R.string.plant_search_title)
}

Saat menavigasi ke tujuan, buat instance rute Anda:

val params = SearchParameters("rose", listOf("available"))
navController.navigate(route = SearchRoute(params))

Parameter ini dapat diperoleh dari rute di tujuan:

val searchRoute = navController().getBackStackEntry<SearchRoute>().toRoute<SearchRoute>()
val params = searchRoute.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 menggunakan DSL Kotlin.

Saat membuat deep link implisit namun, Anda tidak memiliki sumber daya navigasi XML yang dapat dianalisis untuk <deepLink> elemen. Oleh karena itu, Anda tidak dapat mengandalkan penempatan <nav-graph> dalam file AndroidManifest.xml Anda dan harus menambahkan intent memfilter ke aktivitas Anda secara manual. Filter intent yang Anda berikan harus cocok dengan jalur dasar, tindakan, dan mimetype deep link aplikasi Anda.

Deep link ditambahkan ke tujuan dengan memanggil fungsi deepLink di dalam lambda tujuan. Metode ini menerima rute sebagai jenis berparameter, dan parameter basePath untuk jalur dasar URL yang digunakan untuk deep link.

Anda juga dapat menambahkan tindakan dan {i>mimetype<i} menggunakan deepLinkBuilder lambda akhir.

Contoh berikut membuat URI deep link untuk tujuan Home.

@Serializable data object Home

fragment<HomeFragment, Home>{
  deepLink<Home>(basePath = "www.example.com/home"){
    // Optionally, specify the action and/or mime type that this destination
    // supports
    action = "android.intent.action.MY_ACTION"
    mimeType = "image/*"
  }
}

Format URI

Format URI deep link otomatis dibuat dari kolom rute menggunakan aturan berikut:

  • Parameter yang diperlukan ditambahkan sebagai parameter jalur (contoh: /{id})
  • Parameter dengan nilai default (parameter opsional) ditambahkan sebagai parameter kueri (contoh: ?name={name})
  • Koleksi ditambahkan sebagai parameter kueri (contoh: ?items={value1}&items={value2})
  • Urutan parameter sesuai dengan urutan kolom dalam rute

Misalnya, jenis rute berikut:

@Serializable data class PlantDetail(
  val id: String,
  val name: String,
  val colors: List<String>,
  val latinName: String? = null,
)

memiliki format URI yang dihasilkan:

basePath/{id}/{name}/?colors={color1}&colors={color2}&latinName={latinName}

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.

Batasan

Plugin Safe Args tidak kompatibel dengan DSL Kotlin, karena plugin tersebut mencari file resource XML untuk menghasilkan class Directions dan Arguments.