基本を理解して実装する

Navigation は、ユーザーがアプリ内を移動する方法を説明します。ユーザーは通常、タップまたはクリックによって UI 要素を操作し、アプリはそれに応じて新しいコンテンツを表示します。ユーザーが前のコンテンツに戻りたい場合は、[戻る] 操作を使用するか、[戻る] ボタンをタップします。

ナビゲーションの状態をモデル化する

この動作をモデル化する便利な方法は、コンテンツのスタックを使用することです。ユーザーが新しいコンテンツに移動すると、そのコンテンツはスタックの一番上にプッシュされます。 そのコンテンツから戻ると、スタックからポップされ、前のコンテンツが表示されます。 ナビゲーションの用語では、このスタックは通常、ユーザーが戻ることができるコンテンツを表すため、バックスタック と呼ばれます。

ソフトウェア キーボードの操作ボタン(チェックマーク アイコン)が赤で囲まれています。
図 1.ユーザー ナビゲーション イベントによるバックスタックの変化を示す図。

バックスタックを作成する

Navigation 3 では、バックスタックに実際のコンテンツは含まれません。代わりに、 **キー** と呼ばれるコンテンツへの参照が含まれます。キーは任意の型にできますが、通常はシリアル化可能なシンプルなデータクラスです。コンテンツではなく参照を使用することには、次のようなメリットがあります。

  • キーをバックスタックにプッシュするだけで簡単に移動できます。
  • キーがシリアル化可能であれば、バックスタックを永続ストレージに保存できるため、構成の変更やプロセスの終了後も維持できます。ユーザーは、アプリを離れて後で戻ってきたときに、同じコンテンツが表示された状態で中断したところから再開できることを期待するため、これは重要です。詳しくは、バックスタックを保存するをご覧ください。

Navigation 3 API の主なコンセプトは、デベロッパーがバックスタックを所有することです。ライブラリは次のことを想定しています。

  • バックスタックは、スナップショット状態の List<T> であり、 T はバックスタックの keys の型です。Any を使用することも、独自のより厳密な型付きキーを指定することもできます。「プッシュ」または「ポップ」という用語は、リストの末尾からアイテムを追加または削除することを意味します。
  • バックスタックを監視し、その状態を UI に反映します NavDisplay

次の例は、キーとバックスタックを作成し、ユーザー ナビゲーション イベントに応じてバックスタックを変更する方法を示しています。

// 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()
}

キーをコンテンツに解決する

コンテンツは、コンポーズ可能な関数を含むクラスである NavEntry を使用して Navigation 3 でモデル化されます。これは、ユーザーが移動できる単一のコンテンツであるデスティネーションを表します。 前方および後方

NavEntry には、コンテンツに関する情報であるメタデータを含めることもできます。このメタデータは、NavDisplay などのコンテナ オブジェクトによって読み取られ、NavEntry のコンテンツの表示方法を決定するのに役立ちます。たとえば、メタデータを使用して、特定の NavEntry のデフォルトのアニメーションをオーバーライドできます。NavEntry metadata は、String キーから Any 値へのマッピングであり、汎用性の高いデータ ストレージを提供します。

keyNavEntry に変換するには、エントリ プロバイダを作成します。これは、key を受け取り、その keyNavEntry を返す関数です。通常、NavDisplay を作成するときにラムダ パラメータとして定義されます。

エントリ プロバイダを作成する方法は 2 つあります。ラムダ 関数を直接作成する方法と、entryProvider DSL を使用する方法です。

エントリ プロバイダ関数を直接作成する

通常、エントリ プロバイダ関数は、各キーのブランチを含む 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 をシングルペイン レイアウトで表示します。次の録画は、このアプリの実行を示しています。

2 つのデスティネーションがある場合の `NavDisplay` のデフォルトの動作。
図 2.NavDisplay 2 つの デスティネーションでのデフォルトの動作。

デスティネーションのライフサイクル

NavDisplay は、カスタム LifecycleOwners を使用して、 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 のさまざまなオブジェクト間でデータがどのように流れるかを示しています。

Navigation 3 のさまざまなオブジェクト間でデータがどのように流れるかを示す図。
図 3.Navigation 3 のさまざまなオブジェクトをデータがどのように流れるかを示す図。
  1. ナビゲーション イベントによって変更が開始されます 。ユーザーの操作に応じて、キーがバックスタックに追加または削除されます。

  2. バックスタックの状態が変化すると、コンテンツの取得がトリガーされますNavDisplay(バックスタックをレンダリングするコンポーズ可能)は、バックスタックを監視します。デフォルト構成では、一番上のバックスタック エントリをシングルペイン レイアウトで表示します。バックスタックの一番上のキーが変更されると、NavDisplay はこのキーを使用して、エントリ プロバイダから対応するコンテンツをリクエストします。

  3. エントリ プロバイダがコンテンツを提供します 。エントリ プロバイダは、キーを NavEntry に解決する関数です。NavDisplay からキーを受け取ると、エントリ プロバイダは、キーとコンテンツの両方を含む関連付けられた NavEntry を提供します。

  4. コンテンツが表示されますNavDisplayNavEntry を受け取り、コンテンツを表示します。