本指南將說明如何以可序列化的 Kotlin 型別取代以字串為基礎的路徑,確保編譯階段安全無虞,並避免因錯別字或引數型別不正確而導致執行階段當機。
必要條件
開始遷移前,請確認專案符合下列需求:
- Navigation 版本:更新至 Jetpack Navigation 2.8.0 以上版本
- Kotlin 序列化外掛程式:
- 將外掛程式新增至
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" }
- 將依附元件新增至頂層
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。
- 建立自訂型別:
```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,以免不必要的分配 - 可為空值的型別:新版 API 支援可為空值的型別 (例如
data class Search(val query: String?)),並自動提供預設值 - 測試:在 UI 測試期間,使用
navController.currentBackStackEntry?.hasRoute<T>()以型別安全的方式檢查目前的目的地