Compose Multiplatform 中的 Navigation 3
Android 的 Navigation 程式庫已升級至 Navigation 3,引入了重新設計的導覽方法,該方法可與 Compose 搭配使用,並考慮了對先前版本程式庫的回饋。 從 1.10 版本開始,Compose Multiplatform 支援在所有支援平台的多平台專案中採用 Navigation 3: Android、iOS、桌面 (desktop) 和 Web。
主要變更
Navigation 3 不僅僅是程式庫的新版本 —— 在許多方面,它完全是一個新的程式庫。 若要進一步了解這次重新設計背後的理念,請參閱 Android 開發者部落格文章。
Navigation 3 的主要變更包括:
- 使用者擁有的 back stack。您不再操作單一的程式庫 back stack,而是建立並管理一個
SnapshotStateList狀態,UI 會直接觀察該狀態。 - 低階構建區塊。由於與 Compose 的整合更加緊密,該程式庫在實作您自己的導覽組件和行為時提供了更大的靈活性。
- 適應性配置系統。透過適應性設計,您可以同時顯示多個目的地,並在配置之間無縫切換。
在 Android 文件中進一步了解 Navigation 3 的一般設計。
相依性設定
若要嘗試 Navigation 3 的多平台實作,請將以下相依性新增至您的版本目錄:
[versions]
multiplatform-nav3-ui = "1.0.0-alpha05"
[libraries]
jetbrains-navigation3-ui = { module = "org.jetbrains.androidx.navigation3:navigation3-ui", version.ref = "multiplatform-nav3-ui" }雖然 Navigation 3 以兩個構件發佈,分別是
navigation3:navigation3-ui和navigation3:navigation3-common,但只有navigation3-ui具有獨立的 Compose Multiplatform 實作。 對navigation3-common的相依性是透過傳遞方式新增的。
對於使用 Material 3 Adaptive 和 ViewModel 程式庫的專案,還需新增以下導覽支援構件:
[versions]
compose-multiplatform-adaptive = "1.3.0-alpha02"
compose-multiplatform-lifecycle = "2.10.0-alpha05"
[libraries]
jetbrains-material3-adaptiveNavigation3 = { module = "org.jetbrains.compose.material3.adaptive:adaptive-navigation3", version.ref = "compose-multiplatform-adaptive" }
jetbrains-lifecycle-viewmodelNavigation3 = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-navigation3", version.ref = "compose-multiplatform-lifecycle" }最後,您可以嘗試由 JetBrains 工程師建立的概念驗證程式庫。該程式庫將多平台 Navigation 3 與 Web 上的瀏覽器歷程導覽整合在一起:
[versions]
compose-multiplatform-navigation3-browser = "0.2.0"
[libraries]
navigation3-browser = { module = "com.github.terrakok:navigation3-browser", version.ref = "compose-multiplatform-navigation3-browser" }瀏覽器歷程導覽預計將在 1.1.0 版本中由基礎多平台 Navigation 3 程式庫支援。
多平台支援
Navigation 3 與 Compose 緊密結合,允許 Android 導覽實作在通用 Compose Multiplatform 程式碼中以最小的變動運作。 為了支援 Web 和 iOS 等非 JVM 平台,您唯一需要做的就是實作目的地金鑰的多型序列化。
您可以在 GitHub 上比較使用 Navigation 3 的僅限 Android 與多平台應用程式的廣泛範例:
目的地金鑰的多型序列化
在 Android 上,Navigation 3 依賴基於反射的序列化,這在針對 iOS 等非 JVM 平台時是不可用的。 為了考慮到這一點,程式庫為 rememberNavBackStack() 函式提供了兩個多載:
- 第一個多載僅接受一組
NavKey參考,並需要基於反射的序列化器。 - 第二個多載還接受一個
SavedStateConfiguration參數,允許您提供SerializersModule並在所有平台上正確處理開放式多型。
Navigation 3 多平台範例定義了路由並使用 SavedStateConfiguration 進行註冊,如下所示:
@Serializable
private data object RouteA : NavKey
@Serializable
private data class RouteB(val id: String) : NavKey
// 建立開放式多型所需的序列化配置
private val config = SavedStateConfiguration {
serializersModule = SerializersModule {
polymorphic(NavKey::class) {
subclass(RouteA::class, RouteA.serializer())
subclass(RouteB::class, RouteB.serializer())
}
}
}
@Composable
fun BasicDslActivity() {
// 使用序列化配置
val backStack = rememberNavBackStack(config, RouteA)
NavDisplay(
backStack = backStack,
//...
)
}建議的序列化方法
在實作多平台導覽時,您需要選擇如何組織和序列化您的路由定義。根據您專案的複雜程度和模組化程度,請使用以下三種模式之一。
使用密封型別的單一模組
對於所有路由都存在於單一模組的小型專案,請使用 sealed interface。這是最直接的方法,因為 Kotlin 序列化會自動處理階層結構:
@Serializable
sealed interface Route : NavKey
@Serializable
data object RouteA : Route
@Serializable
data class RouteB(val id: String) : Route
// 使用預設序列化器的 Backstack
val backStack: MutableList<Route> =
rememberSerializable(serializer = SnapshotStateListSerializer()) {
mutableStateListOf(RouteA)
}或者,如果您想明確使用 rememberNavBackStack() 函式,這裡有一個稍微不同的組態:
private val config = SavedStateConfiguration {
serializersModule = SerializersModule {
polymorphic(NavKey::class) {
subclassesOfSealed<Route>()
}
}
}
val backStack = rememberNavBackStack(config, RouteA)包含聚合密封型別的多模組
對於路由定義在多個模組中的更複雜專案,您可以為每個模組定義一個密封型別。然後,在 app 模組中使用 subclassesOfSealed() 函式聚合它們的序列化器。
// 模組 A
@Serializable sealed interface FeatureA : NavKey
@Serializable data object RouteA1 : FeatureA
@Serializable data object RouteA2 : FeatureA
// 模組 B
@Serializable sealed interface FeatureB : NavKey
@Serializable data class RouteB1(val id: String) : FeatureB
@Serializable data class RouteB2(val id: String) : FeatureB
// app 模組
private val config = SavedStateConfiguration {
serializersModule = SerializersModule {
polymorphic(NavKey::class) {
subclassesOfSealed<FeatureA>()
subclassesOfSealed<FeatureB>()
}
}
}
val backStack = rememberNavBackStack(config, RouteA1)透過相依注入 (DI),您還可以動態使用 DI 容器將每個模組的密封型別序列化器收集到 Set<KSerializer> 中。
包含個別路由註冊的多模組
如果您的路由無法分組為密封型別,您可以手動組合來自不同模組的 SerializersModule 執行個體。
// 模組 A
@Serializable data object RouteA1 : NavKey
@Serializable data object RouteA2 : NavKey
val serializerModuleA = SerializersModule {
polymorphic(NavKey::class) {
subclass(RouteA1::class, RouteA1.serializer())
subclass(RouteA2::class, RouteA2.serializer())
}
}
// 模組 B
@Serializable data class RouteB1(val id: String) : NavKey
@Serializable data class RouteB2(val id: String) : NavKey
val serializerModuleB = SerializersModule {
polymorphic(NavKey::class) {
subclass(RouteB1::class, RouteB1.serializer())
subclass(RouteB2::class, RouteB2.serializer())
}
}
// app 模組
private val config = SavedStateConfiguration {
serializersModule = serializerModuleA + serializerModuleB
}
val backStack = rememberNavBackStack(config, RouteA1)這種方法提供了高度的靈活性和解耦,儘管它需要更多的手動維護。與包含聚合密封型別的多模組方法類似,您可以使用 DI 動態組合序列化器列表,這可以提高靈活性。
下一步
Android 開發者入口網站對 Navigation 3 進行了深入探討。雖然部分文件使用了 Android 特定的範例,但核心概念和導覽原則在所有平台上都保持一致:
- Navigation 3 總覽,包含管理狀態、模組化導覽程式碼和動畫的建議。
- 從 Navigation 2 遷移到 Navigation 3。將 Navigation 3 視為一個新的程式庫,而不是現有程式庫的新版本會更容易,因此與其說是遷移,不如說是重寫。但該指南指出了應採取的一般步驟。
