指南:在 Compose 和 Navigation 2 中遷移至類型安全導覽

本指南將說明如何以可序列化的 Kotlin 型別取代以字串為基礎的路徑,確保編譯階段安全無虞,並避免因錯別字或引數型別不正確而導致執行階段當機。

必要條件

開始遷移前,請確認專案符合下列需求:

  1. Navigation 版本:更新至 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" }
  • 將依附元件新增至頂層 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,在 composabledialog 函式中使用新的泛型型別。

變更前:

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,以免不必要的分配
  • 可為空值的型別:新版 API 支援可為空值的型別 (例如 data class Search(val query: String?)),並自動提供預設值
  • 測試:在 UI 測試期間,使用 navController.currentBackStackEntry?.hasRoute<T>() 以型別安全的方式檢查目前的目的地