Navigation 3 は、Jetpack Compose がナビゲーション状態を処理する方法の根本的な変化を表しており、Navigation 2 よりもアーキテクチャ上の大きな利点があります。
Wear Compose アプリを Navigation 2 から Navigation 3 に移行するために必要なアーキテクチャの変更と手順について説明します。
Navigation 3 の主なメリット
- バックスタックの直接制御:
NavBackStackは、ユーザーがアクセスした画面の履歴を表すNavKeyオブジェクトの可変リストにすぎません。Kotlin のMutableList(add、removeLast、clear)と同様に制御します。リストを直接操作して、キーを追加して進む、キーを削除して戻るなどのナビゲーション アクションを実行します。 - Compose ファースト設計: バックスタックは標準のオブザーバブル状態としてモデル化されます。ナビゲーション履歴を変更すると、他の Compose 状態を更新する場合とまったく同じように、再コンポーズが自動的にトリガーされ、現在の画面が表示されます。
- デフォルトで型安全: 文字列ベースのルートが完全に排除されます。ナビゲーションでは、シリアル化可能なデータ オブジェクトとデータクラスが使用されます。
- 分離されたプレゼンテーション(シーン戦略): UI 遷移レイヤ(
NavDisplayとSwipeDismissableSceneStrategy)が状態トラッキング(NavBackStack)から完全に分離され、Wear OS の組み込みナビゲーション遷移の統合がよりシンプルになります。
移行手順
1. 依存関係を更新する
古い androidx.wear.compose:compose-navigation 依存関係を削除し、新しい分割された Navigation 3 依存関係と Kotlin シリアル化サポートを導入します。
以下を削除します。
implementation("androidx.wear.compose:compose-navigation:...")
追加:
implementation("androidx.navigation3:navigation3-runtime:...") // State logic
implementation("androidx.navigation3:navigation3-ui:...") // Display logic
implementation("androidx.wear.compose:compose-navigation3:...") // Wear gestures
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:...") // Requires compiler plugin
2. リンク先を更新して NavKey を実装する
Navigation 2 では、ルーティングに文字列または汎用オブジェクトを使用していた可能性があります。Navigation 3 では、NavKey マーカー インターフェースを実装し、すべての画面オブジェクトに @Serializable でアノテーションを付ける必要があります。
この設定が必要な理由バックスタックを保存して、プロセスの終了をまたいで復元できるように、基盤となる navigation3-runtime は kotlinx-serialization に依存して状態をシリアル化します。
以前(ナビゲーション 2 - 汎用的な型安全ルート):
sealed class Nav2Screen { data object Landing : Nav2Screen() data object List : Nav2Screen() }
後(ナビゲーション 3 - NavKey + Serializable):
@Serializable sealed interface MigrationScreen : NavKey { @Serializable data object Landing : MigrationScreen @Serializable data object List : MigrationScreen }
3. ルーティング ロジック(NavController から NavBackStack)を置き換える
NavController を、rememberNavBackStack を使用して初期化された NavBackStack に置き換えます。Wear OS 専用の SwipeDismissableSceneStrategy をインスタンス化する必要もあります。
変更前(ナビゲーション 2):
val navController = rememberSwipeDismissableNavController()
変更後(ナビゲーション 3):
val backStack = rememberNavBackStack(MigrationScreen.Landing as NavKey) val strategy = rememberSwipeDismissableSceneStrategy<NavKey>()
4. NavHost を NavDisplay と entryProvider DSL に置き換える
NavHost コンテナとその内部 composable("route") { ... } ビルダー DSL は、NavDisplay と entryProvider {
entry<Key> { ... } } DSL に置き換えられます。
変更前(ナビゲーション 2):
SwipeDismissableNavHost(navController = navController, startDestination = "menu") { composable("menu") { GreetingScreen( onShowList = { navController.navigate("list") } ) } composable("list") { ListScreen() } }
変更後(ナビゲーション 3):
NavDisplay( backStack = backStack, sceneStrategies = listOf(strategy), entryProvider = entryProvider { entry<MigrationScreen.Landing> { GreetingScreen( onShowList = { backStack.add(MigrationScreen.List) } ) } entry<MigrationScreen.List> { ListScreen() } } )