Navigation は、ユーザーがアプリ内を移動する方法を説明します。ユーザーは通常、タップまたはクリックによって UI 要素を操作し、アプリはそれに応じて新しいコンテンツを表示します。ユーザーが前のコンテンツに戻りたい場合は、[戻る] 操作を使用するか、[戻る] ボタンをタップします。
ナビゲーション状態のモデル化
この動作をモデル化する便利な方法は、コンテンツのスタックを使用することです。ユーザーが新しいコンテンツに進むと、そのコンテンツがスタックの一番上にプッシュされます。ユーザーがそのコンテンツから戻ると、そのコンテンツはスタックからポップされ、前のコンテンツが表示されます。ナビゲーションの用語では、このスタックは通常、バックスタックと呼ばれます。これは、ユーザーが戻ることができるコンテンツを表しているためです。
バックスタックを作成する
Navigation 3 では、バックスタックにコンテンツは含まれません。代わりに、キーと呼ばれるコンテンツへの参照が含まれています。キーは任意の型にできますが、通常はシンプルなシリアル化可能なデータクラスです。コンテンツではなく参照を使用すると、次のようなメリットがあります。
- バックスタックにキーをプッシュすることで、簡単にナビゲーションできます。
- キーがシリアル化可能である限り、バックスタックを永続ストレージに保存できるため、構成変更やプロセスの終了後も存続できます。これは重要なことです。ユーザーは、アプリを終了して後で戻ってきたときに、同じコンテンツが表示され、中断したところから再開できることを期待しているからです。詳細については、バックスタックを保存するをご覧ください。
Navigation 3 API の主なコンセプトは、デベロッパーがバックスタックを所有することです。ライブラリ:
- バックスタックがスナップショット状態をバックアップした
List<T>であることを想定しています。ここで、Tはバックスタックkeysのタイプです。Anyを使用することも、より厳密に型指定された独自のキーを指定することもできます。「push」または「pop」という用語は、リストの末尾にアイテムを追加または削除する実装を意味します。 - バックスタックを監視し、
NavDisplayを使用してその状態を UI に反映します。
次の例は、キーとバックスタックを作成し、ユーザー ナビゲーション イベントに応じてバックスタックを変更する方法を示しています。
// Define keys that will identify content data object ProductList data class ProductDetail(val id: String) @Composable fun MyApp() { // Create a back stack, specifying the key the app should start with val backStack = remember { mutableStateListOf<Any>(ProductList) } // Supply your back stack to a NavDisplay so it can reflect changes in the UI // ...more on this below... // Push a key onto the back stack (navigate forward), the navigation library will reflect the change in state backStack.add(ProductDetail(id = "ABC")) // Pop a key off the back stack (navigate back), the navigation library will reflect the change in state backStack.removeLastOrNull() }
キーをコンテンツに解決する
Navigation 3 では、コンポーズ可能な関数を含むクラスである NavEntry を使用してコンテンツがモデル化されます。これは、デスティネーション(ユーザーが進むまたは戻ることができる単一のコンテンツ)を表します。
NavEntry には、コンテンツに関する情報であるメタデータを含めることもできます。このメタデータは、NavDisplay などのコンテナ オブジェクトによって読み取られ、NavEntry のコンテンツの表示方法を決定するのに役立ちます。たとえば、メタデータを使用して、特定の NavEntry のデフォルトのアニメーションをオーバーライドできます。NavEntry metadata は、String キーを Any 値にマッピングするもので、汎用性の高いデータ ストレージを提供します。
key を NavEntry に変換するには、エントリ プロバイダを作成します。これは、key を受け取り、その key の NavEntry を返す関数です。通常は、NavDisplay の作成時にラムダ パラメータとして定義されます。
エントリ プロバイダを作成するには、ラムダ関数を直接作成するか、entryProvider DSL を使用するかの 2 つの方法があります。
エントリ プロバイダ関数を直接作成する
通常、Entry Provider 関数は when ステートメントを使用して作成し、キーごとにブランチを作成します。
entryProvider = { key -> when (key) { is ProductList -> NavEntry(key) { Text("Product List") } is ProductDetail -> NavEntry( key, metadata = mapOf("extraDataKey" to "extraDataValue") ) { Text("Product ${key.id} ") } else -> { NavEntry(Unit) { Text(text = "Invalid Key: $it") } } } }
entryProvider DSL を使用する
entryProvider DSL を使用すると、各キータイプに対してテストを行い、それぞれに NavEntry を作成する必要がなくなるため、ラムダ関数を簡素化できます。これには entryProvider ビルダー関数を使用します。また、キーが見つからない場合のデフォルトのフォールバック動作(エラーをスローする)も含まれています。
entryProvider = entryProvider { entry<ProductList> { Text("Product List") } entry<ProductDetail>( metadata = mapOf("extraDataKey" to "extraDataValue") ) { key -> Text("Product ${key.id} ") } }
このスニペットでは、次の点に注意してください。
entryは、指定されたタイプとコンポーズ可能なコンテンツを持つNavEntryを定義するために使用されます。entryは、NavEntry.metadataを設定するmetadataパラメータを受け取ります。
バックスタックを表示する
バックスタックは、アプリのナビゲーション状態を表します。バックスタックが変更されるたびに、アプリの UI に新しいバックスタックの状態が反映される必要があります。Navigation 3 では、NavDisplay がバックスタックを監視し、それに応じて UI を更新します。次のパラメータを使用して作成します。
- バックスタック -
SnapshotStateList<T>型にする必要があります。ここで、Tはバックスタック キーの型です。これは監視可能なListであるため、変更されるとNavDisplayの再コンポーズがトリガーされます。 - バックスタック内のキーを
NavEntryオブジェクトに変換するentryProvider。 - 必要に応じて、
onBackパラメータにラムダを指定します。これは、ユーザーが戻るイベントをトリガーしたときに呼び出されます。
次の例は、NavDisplay を作成する方法を示しています。
data object Home data class Product(val id: String) @Composable fun NavExample() { val backStack = remember { mutableStateListOf<Any>(Home) } NavDisplay( backStack = backStack, onBack = { backStack.removeLastOrNull() }, entryProvider = { key -> when (key) { is Home -> NavEntry(key) { ContentGreen("Welcome to Nav3") { Button(onClick = { backStack.add(Product("123")) }) { Text("Click to navigate") } } } is Product -> NavEntry(key) { ContentBlue("Product ${key.id} ") } else -> NavEntry(Unit) { Text("Unknown route") } } } ) }
デフォルトでは、NavDisplay は、シングルペイン レイアウトのバックスタックの最上位の NavEntry を表示します。次の動画は、このアプリの動作を示しています。
NavDisplay のデフォルトの動作(2 つの宛先)。宛先のライフサイクル
NavDisplay は、カスタム LifecycleOwner を使用して、Scene レベルの制約と Entry レベルの制約の両方に基づいて NavEntry のライフサイクル状態を制限します。
Compose のライフサイクルの詳細については、Jetpack Compose のライフサイクルをご覧ください。
シーンレベルのライフサイクルの制約
NavDisplay は、アクティブな Scene のライフサイクルを管理します。シーンレベルの上限は次のように決定されます。
オーバーレイ以外のシーンの場合:
RESUMED: シーンの遷移が完了し、その上にアクティブなオーバーレイ シーンが表示されていない場合にのみ許可されます。STARTED: 前後に移動する場合や、オーバーレイで覆われている場合など、シーンの遷移中はSTARTEDに制限されます。
ダイアログやボトムシートなどのオーバーレイ シーンの場合:
RESUMED: 最上位の現在アクティブなオーバーレイ シーンでのみ許可されます。STARTED: 新しいオーバーレイで覆われている、基盤となるオーバーレイ シーンではSTARTEDを上限とします。
エントリレベルのライフサイクル状態
ライブラリは、バックスタック内の存在に基づいて、個々の NavEntry の最大ライフサイクル状態を管理します。
RESUMED: エントリが現在のバックスタックに存在する場合、そのライフサイクルはRESUMEDまで進むことができます(シーンレベルの上限が適用されます)。CREATED: エントリがバックスタックに存在しない場合(ポップされたが、アニメーション化中に画面にレンダリングされている場合など)、ライブラリはライフサイクルをCREATEDに厳密に制限します。この制限により、バックグラウンドまたは終了中のエントリは、終了遷移を完了する間、フローの収集やRESUMED状態またはSTARTED状態にバインドされたコルーチンの起動などのアクティブな処理の実行を停止します。
組み合わせ
たとえば、NavEntry の最終的なライフサイクル状態は次のように解決されます。
| シナリオ | シーンレベルの上限 | エントリーレベルの上限 | 有効な上限 |
|---|---|---|---|
| アクティブなエントリ、固定画面(遷移やオーバーレイなし) | RESUMED |
RESUMED |
RESUMED |
| アクティブなエントリ、遷移中(移動中または移動元) | STARTED |
RESUMED |
STARTED |
| アクティブなエントリ、オーバーレイで覆われている(ダイアログが開いているなど) | STARTED |
RESUMED |
STARTED |
| ポップされたエントリ、アニメーションで消える | STARTED または RESUMED |
CREATED |
CREATED |
すべてを組み合わせる
次の図は、Navigation 3 のさまざまなオブジェクト間でデータがどのように流れるかを示しています。
ナビゲーション イベントによって変更が開始されます。キーは、ユーザーの操作に応じてバックスタックに追加または削除されます。
バックスタックの状態が変化すると、コンテンツの取得がトリガーされます。
NavDisplay(バックスタックをレンダリングするコンポーザブル)は、バックスタックを監視します。デフォルトの構成では、単一ペイン レイアウトで最上位のバックスタック エントリが表示されます。バックスタックの一番上のキーが変更されると、NavDisplayはこのキーを使用して、対応するコンテンツをエントリ プロバイダにリクエストします。エントリ プロバイダがコンテンツを提供する。エントリ プロバイダは、キーを
NavEntryに解決する関数です。エントリ プロバイダは、NavDisplayからキーを受け取ると、関連付けられたNavEntryを提供します。これにはキーとコンテンツの両方が含まれます。コンテンツが表示される。
NavDisplayはNavEntryを受信してコンテンツを表示します。