Fragment และ Kotlin DSL

คอมโพเนนต์การนำทางมีภาษาเฉพาะโดเมนตาม Kotlin หรือ DSL ที่ใช้ประเภท type-safe ของ Kotlin เครื่องมือสร้าง ที่ใช้เวลาเพียง 2 นาที API นี้ช่วยให้คุณเขียนกราฟในโค้ด Kotlin ได้อย่างชัดเจนแทน มากกว่าภายในทรัพยากร XML ซึ่งจะเป็นประโยชน์หากคุณต้องการสร้างแอป การนำทางแบบไดนามิก เช่น แอปอาจดาวน์โหลดและแคช การกำหนดค่าการนำทางจากบริการเว็บภายนอกแล้วใช้ เพื่อสร้างกราฟการนำทางแบบไดนามิกใน onCreate()

การขึ้นต่อกัน

หากต้องการใช้ Kotlin DSL กับ Fragments ให้เพิ่มทรัพยากร Dependency ต่อไปนี้ในส่วน build.gradle ไฟล์:

Groovy

dependencies {
    def nav_version = "2.8.4"

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

Kotlin

dependencies {
    val nav_version = "2.8.4"

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

การสร้างกราฟ

ต่อไปนี้เป็นตัวอย่างพื้นฐานที่อ้างอิงจากดอกทานตะวัน แอป สำหรับกรณีนี้ ตัวอย่างเช่น เรามีปลายทาง 2 แห่ง ได้แก่ home และ plant_detail home ปลายทาง จะแสดงเมื่อผู้ใช้เปิดแอปเป็นครั้งแรก จุดหมายนี้ แสดงรายการต้นไม้จากสวนของผู้ใช้ เมื่อผู้ใช้เลือกหนึ่งใน ต้นไม้ แอปจะนำทางไปยังจุดหมาย plant_detail

รูปที่ 1 แสดงปลายทางเหล่านี้พร้อมกับอาร์กิวเมนต์ที่แอตทริบิวต์ ปลายทาง plant_detail และการดำเนินการ to_plant_detail ที่แอปใช้ เพื่อนำทางจาก home ไป plant_detail

วันที่ แอป Sunflower มีจุดหมาย 2 แห่ง รวมถึงการดำเนินการที่
            เพื่อเชื่อมโยงสิ่งต่างๆ เข้าด้วยกัน
รูปที่ 1 แอป Sunflower มี 2 จุดหมาย home และ plant_detail รวมถึงการดำเนินการที่ เพื่อเชื่อมโยงสิ่งต่างๆ เข้าด้วยกัน

โฮสติ้ง Kotlin DSL Nav Graph

ก่อนที่คุณจะสร้างกราฟการนำทางของแอปได้ คุณต้องมีสถานที่เพื่อโฮสต์ กราฟ ตัวอย่างนี้ใช้ Fragment จึงโฮสต์กราฟใน NavHostFragment ภายใน 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>

โปรดสังเกตว่าไม่มีการตั้งค่าแอตทริบิวต์ app:navGraph ในตัวอย่างนี้ กราฟ ไม่ได้หมายถึงเป็นทรัพยากรใน โฟลเดอร์ res/navigation จึงต้องมีการตั้งค่าให้เป็นส่วนหนึ่งของ onCreate() ในกิจกรรม

ใน XML การทำงานจะเชื่อมโยงรหัสปลายทางเข้ากับอาร์กิวเมนต์อย่างน้อย 1 รายการ แต่เมื่อใช้ DSL การนำทาง เส้นทางอาจมีอาร์กิวเมนต์เป็นส่วนหนึ่งของ เส้นทาง ซึ่งหมายความว่าจะไม่มีแนวคิดเกี่ยวกับการดำเนินการเมื่อใช้ DSL

ขั้นตอนถัดไปคือการกำหนดเส้นทางที่คุณจะใช้ในการกำหนด กราฟ

สร้างเส้นทางสำหรับกราฟของคุณ

กราฟการนำทางแบบ XML ถูกแยกวิเคราะห์เป็นส่วนหนึ่งของ ของกระบวนการบิลด์ของ Android ระบบจะสร้างค่าคงที่ตัวเลขสำหรับ id แต่ละรายการ ที่กำหนดไว้ในกราฟ รหัสแบบคงที่ที่สร้างขึ้นในเวลาบิลด์เหล่านี้ไม่ได้ พร้อมใช้งานเมื่อสร้างกราฟการนำทางขณะรันไทม์ เพื่อให้ DSL การนำทาง ใช้ ซีเรียลได้ ประเภท แทนที่จะเป็น รหัส แต่ละเส้นทางจะแสดงด้วยประเภทที่ไม่ซ้ำกัน

เมื่อจัดการกับอาร์กิวเมนต์ อาร์กิวเมนต์เหล่านี้จะสร้างขึ้นในเส้นทาง type ซึ่งจะช่วยให้คุณพิมพ์คำว่า "ปลอดภัย" สำหรับอาร์กิวเมนต์การนำทาง

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

เมื่อกำหนดเส้นทางแล้ว คุณสามารถสร้างกราฟการนำทางได้

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

ในตัวอย่างนี้ เราได้กำหนดปลายทาง Fragment 2 รายการโดยใช้ fragment() ฟังก์ชันเครื่องมือสร้าง DSL ฟังก์ชันนี้ต้องใช้ประเภท อาร์กิวเมนต์ ที่ใช้เวลาเพียง 2 นาที

ก่อนอื่น ชั้นเรียน Fragment ที่สร้าง UI สำหรับปลายทางนี้ การตั้งค่านี้ให้ผลเหมือนกับ การตั้งค่าแอตทริบิวต์ android:name ในปลายทางส่วนย่อยที่กำหนดไว้ โดยใช้ XML

อย่างที่ 2 คือเส้นทาง ต้องเป็นประเภทที่ต่อเนื่องได้ซึ่งเริ่มจาก Any ทั้งนี้ ควรมีอาร์กิวเมนต์การนำทางที่ปลายทางนี้จะใช้ และประเภทของพวกเขา

ฟังก์ชันนี้ยังยอมรับ lambda ที่ไม่บังคับสำหรับการกำหนดค่าเพิ่มเติมด้วย เช่น เป็นป้ายกำกับปลายทาง รวมทั้งฟังก์ชันของเครื่องมือสร้างแบบฝังสำหรับ อาร์กิวเมนต์และ Deep Link

สุดท้าย คุณสามารถนำทางจาก home ไปยัง plant_detail โดยใช้ NavController.navigate() การโทร:

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

ใน PlantDetailFragment คุณสามารถดูอาร์กิวเมนต์การนำทางโดยการเรียก ปัจจุบัน NavBackStackEntry และการโทร toRoute ในไฟล์ดังกล่าวเพื่อรับอินสแตนซ์ของเส้นทาง

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

หาก PlantDetailFragment ใช้ ViewModel อยู่ ให้รับอินสแตนซ์เส้นทางโดยใช้ SavedStateHandle.toRoute

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

ส่วนที่เหลือของคู่มือนี้จะอธิบายองค์ประกอบทั่วไปของกราฟการนำทาง ปลายทาง และวิธีใช้เมื่อสร้างกราฟ

จุดหมาย

Kotlin DSL รองรับปลายทาง 3 ประเภทในตัว ดังนี้ ปลายทาง Fragment, Activity และ NavGraph รายการมีปลายทางของตัวเอง ฟังก์ชันส่วนขยายแบบอินไลน์ที่สร้างและกำหนดค่า ปลายทาง

ปลายทางของ Fragment

fragment() ฟังก์ชัน DSL สามารถทำเป็นพารามิเตอร์ได้ด้วยคลาส Fragment สำหรับ UI และ ประเภทเส้นทางที่ใช้ระบุจุดหมายนี้โดยไม่ซ้ำกัน ตามด้วยแลมบ์ดา ซึ่งคุณสามารถกำหนดค่าเพิ่มเติมตามที่อธิบายไว้ในการนำทาง ด้วยส่วนกราฟ Kotlin DSL

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

จุดหมายของกิจกรรม

activity() ฟังก์ชัน DSL ใช้พารามิเตอร์ประเภทสำหรับเส้นทาง แต่ไม่ได้แปลงเป็นพารามิเตอร์ ประเภทกิจกรรมที่ติดตั้งใช้งาน แต่คุณตั้งค่า activityClass แบบไม่บังคับใน แลมบ์ดาที่ตามหลังอยู่ ความยืดหยุ่นนี้ช่วยให้คุณสามารถกำหนดจุดหมายของกิจกรรมสำหรับ กิจกรรมที่ควรเริ่มโดยโดยนัย Intent โดยลิงก์ ก็จะไม่สมเหตุสมผล เช่นเดียวกับปลายทางของส่วนย่อย คุณยังสามารถ กำหนดค่าป้ายกำกับ อาร์กิวเมนต์ที่กำหนดเอง และ Deep Link

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

   activityClass = MyActivity::class
}

navigation() สามารถใช้ฟังก์ชัน DSL เพื่อสร้างการนำทางที่ซ้อนกัน กราฟ ฟังก์ชันนี้ใช้ประเภท สำหรับเส้นทางที่กำหนดให้กับกราฟนี้ และมีอาร์กิวเมนต์ 2 แบบ ดังนี้ เส้นทางไปยังจุดหมายเริ่มต้นของกราฟ และ lambda เพื่อไปให้ไกลขึ้น กำหนดค่ากราฟ องค์ประกอบที่ถูกต้องรวมถึงปลายทางอื่นๆ, อาร์กิวเมนต์ที่กำหนดเอง ประเภท ลิงก์ในรายละเอียด และป้ายกำกับที่อธิบายสำหรับ ปลายทาง ป้ายกำกับนี้อาจเป็นประโยชน์ในการเชื่อมโยงกราฟการนำทางกับคอมโพเนนต์ UI โดยใช้ NavigationUI

@Serializable data object HomeGraph
@Serializable data object Home

navigation<HomeGraph>(startDestination = Home) {
   // label, other destinations, deep links
}

การรองรับปลายทางที่กำหนดเอง

หากคุณใช้ประเภทปลายทางใหม่ ที่ไม่รองรับ Kotlin DSL โดยตรง คุณจะเพิ่มปลายทางเหล่านี้ได้ Kotlin DSL โดยใช้ addDestination()

// The NavigatorProvider is retrieved from the NavController
val customDestination = navigatorProvider[CustomNavigator::class].createDestination().apply {
    route = Graph.CustomDestination.route
}
addDestination(customDestination)

หรืออีกวิธีหนึ่ง คุณสามารถใช้โอเปอเรเตอร์เครื่องหมายบวก (Unary Plus) เพื่อเพิ่ม ปลายทางที่สร้างขึ้นโดยตรงลงในกราฟ

// The NavigatorProvider is retrieved from the NavController
+navigatorProvider[CustomNavigator::class].createDestination().apply {
    route = Graph.CustomDestination.route
}

การระบุอาร์กิวเมนต์ปลายทาง

คุณกำหนดอาร์กิวเมนต์ปลายทางให้เป็นส่วนหนึ่งของคลาสเส้นทางได้ สิ่งเหล่านี้สามารถเป็นได้ ในลักษณะเดียวกับที่คุณทำสำหรับคลาส Kotlin ทั่วไป อาร์กิวเมนต์ที่จำเป็นคือ กำหนดเป็นประเภทที่ไม่เป็นค่าว่างและอาร์กิวเมนต์ที่เลือกระบุโดยค่าเริ่มต้น

กลไกที่สำคัญสำหรับการแสดงเส้นทางและอาร์กิวเมนต์คือสตริง อ้างอิง การใช้สตริงเพื่อสร้างรูปแบบเส้นทางจะช่วยให้สามารถจัดเก็บสถานะการนำทาง และ กู้คืนจากดิสก์ในระหว่างการกำหนดค่า การเปลี่ยนแปลง และกระบวนการที่เริ่มต้นโดยระบบ การเสียชีวิต ด้วยเหตุนี้ อาร์กิวเมนต์การนำทางแต่ละรายการจะต้องทำให้เป็นอนุกรมได้ กล่าวคือ ซึ่งจะแปลงการแสดงค่าอาร์กิวเมนต์ในหน่วยความจำเป็นค่าอาร์กิวเมนต์ String

การเรียงอันดับ Kotlin ปลั๊กอิน สร้างวิธีการทำให้ต่อเนื่องโดยอัตโนมัติสำหรับขั้นพื้นฐาน เมื่อ เพิ่มคำอธิบายประกอบ @Serializable ลงในออบเจ็กต์แล้ว

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

fragment<MyFragment, MyRoute>

การระบุประเภทที่กำหนดเอง

สำหรับประเภทอาร์กิวเมนต์ที่กำหนดเอง คุณจะต้องระบุคลาส NavType ที่กำหนดเอง ช่วงเวลานี้ ช่วยให้คุณควบคุมการแยกวิเคราะห์ประเภทของคุณจากเส้นทางหรือลิงก์ในรายละเอียดได้

ตัวอย่างเช่น เส้นทางที่ใช้กำหนดหน้าจอค้นหาอาจมีคลาสที่ แสดงพารามิเตอร์การค้นหา ดังนี้

@Serializable
data class SearchRoute(val parameters: SearchParameters)

@Serializable
data class SearchParameters(
  val searchQuery: String,
  val filters: List<String>
)

NavType ที่กำหนดเองอาจเขียนเป็น

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

จากนั้นจะใช้ใน Kotlin DSL ได้เช่นเดียวกับประเภทอื่นๆ

fragment<SearchFragment, SearchRoute> {
    label = getString(R.string.plant_search_title)
    typeMap = mapOf(typeOf<SearchParameters>() to SearchParametersType)
}

เมื่อนำทางไปยังจุดหมาย ให้สร้างอินสแตนซ์ของเส้นทางของคุณ

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

รับพารามิเตอร์จากเส้นทางในปลายทางได้

val searchRoute = navController().getBackStackEntry<SearchRoute>().toRoute<SearchRoute>()
val params = searchRoute.parameters

Deep Link

คุณเพิ่ม Deep Link ไปยังปลายทางใดก็ได้ เช่นเดียวกับที่เพิ่ม Deep Link ด้วยการใช้ XML กราฟการนำทาง ขั้นตอนเดียวกันทั้งหมดที่ระบุไว้ในหัวข้อการสร้าง Deep Link สําหรับปลายทางจะมีผลกับกระบวนการ ของการสร้าง Deep Link โดยใช้ Kotlin DSL

เมื่อสร้าง Implicit Deep Link แต่คุณไม่มีทรัพยากรการนำทาง XML ที่สามารถวิเคราะห์ <deepLink> องค์ประกอบ ดังนั้น คุณจึงไม่สามารถวาง <nav-graph> ในไฟล์ AndroidManifest.xml และต้องเพิ่ม intent แทน ตัวกรองไปยังกิจกรรมของคุณด้วยตนเอง ความตั้งใจ ตัวกรองที่คุณระบุควรตรงกับเส้นทางพื้นฐาน การกระทำ และประเภท MIME ของ Deep Link ของแอปของคุณ

ระบบจะเพิ่ม Deep Link ไปยังปลายทางโดยการเรียกใช้ฟังก์ชัน deepLink ภายใน เรือแลมบ์ดาของปลายทาง ระบบยอมรับเส้นทางเป็นประเภทพารามิเตอร์ และ พารามิเตอร์ basePath สำหรับเส้นทางฐานของ URL ที่ใช้สำหรับ Deep Link

คุณยังสามารถเพิ่มการดำเนินการและ mimetype โดยใช้เมธอด deepLinkBuilder ตามหลัง lambda

ตัวอย่างต่อไปนี้สร้าง URI ของ Deep Link สำหรับปลายทาง 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/*"
  }
}

รูปแบบ URI

รูปแบบ URI ของ Deep Link จะสร้างขึ้นโดยอัตโนมัติจากช่องของเส้นทาง โดยใช้กฎต่อไปนี้

  • พารามิเตอร์ที่จําเป็นจะต่อท้ายเป็นพารามิเตอร์เส้นทาง (เช่น /{id})
  • พารามิเตอร์ที่มีค่าเริ่มต้น (พารามิเตอร์ที่ไม่บังคับ) จะเพิ่มเป็น query พารามิเตอร์ (เช่น ?name={name})
  • ระบบจะเพิ่มคอลเล็กชันต่อท้ายเป็นพารามิเตอร์การค้นหา (เช่น ?items={value1}&items={value2})
  • ลำดับของพารามิเตอร์ตรงกับลำดับของฟิลด์ในเส้นทาง

ตัวอย่างเช่น ประเภทเส้นทางต่อไปนี้

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

มีรูปแบบ URI ที่สร้างขึ้นเป็น

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

คุณเพิ่ม Deep Link ได้ไม่จำกัดจำนวน ทุกครั้งที่คุณโทร deepLink() Deep Link ใหม่จะต่อท้ายรายการที่เก็บไว้สำหรับปลายทางนั้น

ข้อจำกัด

ปลั๊กอิน Safe Args คือ ไม่สามารถทำงานร่วมกับ Kotlin DSL เนื่องจากปลั๊กอินจะค้นหาไฟล์ทรัพยากร XML เพื่อทำสิ่งต่อไปนี้ สร้างชั้นเรียน Directions และ Arguments