สร้างกราฟแบบเป็นโปรแกรมโดยใช้ Kotlin DSL

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

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

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

ดึงดูด

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

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

เรามาเริ่มด้วยตัวอย่างพื้นฐานตาม แอป Sunflower สำหรับกรณีนี้ ตัวอย่างเช่น เรามีปลายทาง 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 การนำทางใช้สตริงเส้นทางแทนรหัส แต่ละเส้นทางจะแสดงด้วย สตริงที่ไม่ซ้ำกันและเป็นแนวทางปฏิบัติที่ดีที่จะกำหนดค่าเหล่านี้เป็นค่าคงที่เพื่อลด ความเสี่ยงที่จะเกิดข้อบกพร่อง เกี่ยวกับการพิมพ์ผิด

เมื่อจัดการกับอาร์กิวเมนต์ ปัจจัยเหล่านี้จะ อยู่ในสตริงเส้นทาง การสร้างตรรกะนี้ลงในเส้นทางจะลดความเสี่ยงของ ข้อบกพร่องเกี่ยวกับการพิมพ์ผิดที่ถาโถมเข้ามา

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

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

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

ในตัวอย่างนี้ lambda ต่อท้ายจะกำหนดปลายทาง Fragment 2 รายการโดยใช้ fragment() ฟังก์ชันเครื่องมือสร้าง DSL ฟังก์ชันนี้ต้องใช้สตริงเส้นทางสำหรับปลายทาง ซึ่งได้จากค่าคงที่ ฟังก์ชันนี้ยังยอมรับตัวเลือก lambda เพื่อกำหนดค่าเพิ่มเติม เช่น ป้ายกำกับปลายทาง รวมถึง ฟังก์ชันเครื่องมือสร้างแบบฝังสำหรับอาร์กิวเมนต์และ Deep Link

ชั้นเรียน Fragment ที่ จัดการ UI ของแต่ละปลายทางที่ส่งผ่านเป็นประเภทพารามิเตอร์ภายใน วงเล็บสามเหลี่ยม (<>) ซึ่งจะมีผลเหมือนกับการตั้งค่า android:name ในปลายทางส่วนย่อยที่กำหนดไว้โดยใช้ XML

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

private fun navigateToPlant(plantId: String) {
   findNavController().navigate("${nav_routes.plant_detail}/$plantId")
}

ใน PlantDetailFragment คุณจะหาค่าของอาร์กิวเมนต์ตามที่แสดงไว้ได้ ในตัวอย่างต่อไปนี้

val plantId: String? = arguments?.getString(nav_arguments.plant_id)

ดูรายละเอียดเกี่ยวกับวิธีระบุอาร์กิวเมนต์เมื่อไปยังส่วนต่างๆ ได้ใน การให้อาร์กิวเมนต์ปลายทาง

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

จุดหมาย

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

ปลายทางของ Fragment

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

fragment<FragmentDestination>(nav_routes.route_name) {
   label = getString(R.string.fragment_title)
   // arguments, deepLinks
}

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

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

activity(nav_routes.route_name) {
   label = getString(R.string.activity_title)
   // arguments, deepLinks...

   activityClass = ActivityDestination::class
}

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

navigation("route_to_this_graph", nav_routes.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
}

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

ปลายทางใดก็ตามจะกำหนดอาร์กิวเมนต์ที่ไม่บังคับหรือจำเป็นได้ การทำงาน สามารถกำหนดได้โดยใช้ argument() ใน NavDestinationBuilder ซึ่งเป็นคลาสพื้นฐานสำหรับ ประเภทเครื่องมือสร้างปลายทาง ฟังก์ชันนี้ใช้ชื่อของอาร์กิวเมนต์เป็นสตริง และ lambda ที่ใช้สร้างและกำหนดค่า NavArgument

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

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

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

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

บางประเภท เช่น ParcelableType และ SerializableType, ไม่รองรับการแยกวิเคราะห์ค่าจากสตริงที่เส้นทางหรือ Deep Link ใช้ เนื่องจากไม่ได้อาศัยการสะท้อนขณะรันไทม์ ด้วยการระบุช่องที่กำหนดเอง NavType คุณสามารถควบคุมได้ว่าต้องการแยกวิเคราะห์ประเภทของคุณจากเส้นทางอย่างไร หรือ ลิงก์ในรายละเอียด ซึ่งช่วยให้คุณสามารถใช้ การทำให้เป็นอนุกรมของ Kotlin หรืออื่นๆ เพื่อเข้ารหัสและถอดรหัสประเภทที่กำหนดเองแบบไร้การสะท้อน

เช่น คลาสข้อมูลที่แสดงพารามิเตอร์การค้นหาที่ส่งไปยัง สามารถใช้ทั้ง Serializable (เพื่อแสดง การรองรับการเข้ารหัส/ถอดรหัส) และ Parcelize (เพื่อรองรับการบันทึกไปยังและการคืนค่า จาก Bundle):

@Serializable
@Parcelize
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>(nav_routes.plant_search) {
    label = getString(R.string.plant_search_title)
    argument(nav_arguments.search_parameters) {
        type = SearchParametersType
        defaultValue = SearchParameters("cactus", emptyList())
    }
}

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

val params = SearchParameters("rose", listOf("available"))
val searchArgument = SearchParametersType.serializeAsValue(params)
navController.navigate("${nav_routes.plant_search}/$searchArgument")

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

val params: SearchParameters? = arguments?.getParcelable(nav_arguments.search_parameters)

Deep Link

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

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

คุณสามารถระบุ deeplink ที่เจาะจงมากขึ้นสําหรับ Deep Link แต่ละรายการ ปลายทางโดยใช้ deepLink() ฟังก์ชัน DSL ฟังก์ชันนี้ยอมรับ NavDeepLink ที่มี String แสดงรูปแบบ URI, String แสดงการดำเนินการของ Intent และ String แสดง mimeType

เช่น

deepLink {
    uriPattern = "http://www.example.com/plants/"
    action = "android.intent.action.MY_ACTION"
    mimeType = "image/*"
}

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

สถานการณ์ Deep Link โดยนัยที่ซับซ้อนมากขึ้นซึ่งยังกำหนดเส้นทางและ พารามิเตอร์ตามข้อความค้นหาแสดงอยู่ด้านล่าง

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

คุณสามารถใช้ การประมาณค่าในช่วงสตริง เพื่อลดความซับซ้อนของคำจำกัดความ

ข้อจำกัด

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

ดูข้อมูลเพิ่มเติม

ดูความปลอดภัยในประเภทการนำทาง หน้าเพื่อดูวิธีมอบความปลอดภัยตามประเภทให้แก่ Kotlin DSL และ โค้ดเขียนการนำทาง