คำแนะนำ: การย้ายข้อมูลไปยังการนำทางที่ปลอดภัยประเภทใน Compose และ Navigation 2

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

สิ่งที่ต้องมีก่อน

ก่อนเริ่มการย้ายข้อมูล ให้ตรวจสอบว่าโปรเจ็กต์เป็นไปตามข้อกำหนดต่อไปนี้

  1. เวอร์ชันการนำทาง: อัปเดตเป็น Jetpack Navigation 2.8.0 ขึ้นไป
  2. ปลั๊กอินการซีเรียลไลซ์ Kotlin:
  3. เพิ่มปลั๊กอินไปยัง libs.versions.toml โดยทำดังนี้
[libraries]
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }

[plugins]
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
  • เพิ่มทรัพยากร Dependency ลงใน build.gradle.kts ระดับบนสุดและ build.gradle.kts ระดับโมดูล

ขั้นตอนที่ 1: กำหนดปลายทาง

แทนที่สตริงเส้นทางคงที่ด้วยออบเจ็กต์และคลาส @Serializable

  • สำหรับหน้าจอที่ไม่มีอาร์กิวเมนต์ ให้ใช้ data object
  • สำหรับหน้าจอที่มีอาร์กิวเมนต์: ใช้ data class

ก่อน (อิงตามสตริง):

const val ROUTE_HOME = "home"
const val ROUTE_PROFILE = "profile/{userId}"

หลัง (ประเภทปลอดภัย):

import kotlinx.serialization.Serializable

@Serializable
object Home

@Serializable
data class Profile(val userId: String)

ขั้นตอนที่ 2: อัปเดตการกำหนดค่า NavHost

อัปเดต NavHost เพื่อใช้ประเภททั่วไปใหม่ในฟังก์ชัน composable และ dialog

ก่อน

NavHost(navController, startDestination = "home") {
    composable("home") { HomeScreen(...) }
    composable("profile/{userId}") { backStackEntry ->
        val userId = backStackEntry.arguments?.getString("userId")
        ProfileScreen(userId)
    }
}

หลัง

NavHost(navController, startDestination = Home) {
    composable<Home> {
        HomeScreen(...)
    }
    composable<Profile> { backStackEntry ->
        // The library automatically handles argument extraction
        val profile: Profile = backStackEntry.toRoute()
        ProfileScreen(profile.userId)
    }
}

ขั้นตอนที่ 3: ใช้การเรียกการนำทางที่ปลอดภัยประเภท

แทนที่การเรียกการนำทางที่แทรกสตริงด้วยอินสแตนซ์ของคลาส

ก่อน

navController.navigate("profile/user123")

หลัง

navController.navigate(Profile(userId = "user123"))

ขั้นตอนที่ 4: การเข้าถึงอาร์กิวเมนต์ใน ViewModel

หากใช้ ViewModel ตอนนี้คุณสามารถแยกออบเจ็กต์เส้นทางจาก SavedStateHandle ได้โดยตรง

การใช้งาน

class ProfileViewModel(
    savedStateHandle: SavedStateHandle
) : ViewModel() {
    // Automatically parses arguments into the Profile class
    private val profile = savedStateHandle.toRoute<Profile>()
    val userId = profile.userId
}

ขั้นตอนที่ 5: (ขั้นสูง) การจัดการประเภทที่กำหนดเอง

หากต้องการส่งคลาสข้อมูลที่ซับซ้อน (ไม่ใช่แค่ประเภทข้อมูลพื้นฐาน) คุณต้องกําหนดNavTypeที่กําหนดเอง

  1. สร้างประเภทที่กำหนดเอง ```kotlin val SearchFilterType = object : NavType(isNullableAllowed = false) { override fun get(bundle: Bundle, key: String): SearchFilter? = Json.decodeFromString(bundle.getString(key) ?: return null)
override fun parseValue(value: String): SearchFilter =
    Json.decodeFromString(Uri.decode(value))

override fun put(bundle: Bundle, key: String, value: SearchFilter) {
    bundle.putString(key, Json.encodeToString(value))
}

}



2. **Register it in the Graph**:
```kotlin
composable<Search>(
    typeMap = mapOf(typeOf<SearchFilter>() to SearchFilterType)
) { ... }

แนวทางปฏิบัติแนะนำและเคล็ดลับ

  • ลำดับชั้นที่ปิดผนึก: สำหรับแอปขนาดใหญ่ ให้จัดกลุ่มเส้นทางโดยใช้อินเทอร์เฟซหรือคลาสที่ปิดผนึกเพื่อจัดระเบียบโครงสร้างการนำทาง
  • อินสแตนซ์ของออบเจ็กต์: สำหรับเส้นทางที่ไม่มีพารามิเตอร์ ให้ใช้ object เสมอ แทน class เพื่อหลีกเลี่ยงการจัดสรรที่ไม่จำเป็น
  • ประเภทที่กำหนดให้เป็น Null ได้: API ใหม่รองรับประเภทที่กำหนดให้เป็น Null ได้ (เช่น data class Search(val query: String?)) และระบุค่าเริ่มต้นโดยอัตโนมัติ
  • การทดสอบ: ใช้ navController.currentBackStackEntry?.hasRoute<T>() เพื่อ ตรวจสอบปลายทางปัจจุบันในลักษณะที่ปลอดภัยต่อประเภทระหว่างการทดสอบ UI