基本を理解して実装する

ナビゲーションとは、ユーザーがアプリ内を移動する方法のことです。ユーザーは通常、タップまたはクリックして UI 要素を操作し、アプリは新しいコンテンツを表示して応答します。ユーザーが前のコンテンツに戻りたい場合は、戻るジェスチャーを使用するか、戻るボタンをタップします。

ナビゲーション状態のモデリング

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

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

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

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

コンテンツへのキーを解決する

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

NavEntry には、コンテンツに関するメタデータも含めることができます。このメタデータは、NavDisplay などのコンテナ オブジェクトによって読み取られ、NavEntry のコンテンツの表示方法を決定するために使用されます。たとえば、メタデータを使用して、特定の NavEntry のデフォルトのアニメーションをオーバーライドできます。NavEntry metadata は、String キーと Any 値のマップで、多目的なデータ ストレージを提供します。

keyNavEntry に変換するには、entryProvider を作成します。これは、key を受け取り、その keyNavEntry を返す関数です。通常、NavDisplay の作成時にラムダ パラメータとして定義されます。

entryProvider を作成するには、ラムダ関数を直接作成するか、entryProvider DSL を使用するかの 2 つの方法があります。

entryProvider 関数を直接作成する

通常、when ステートメントを使用して entryProvider 関数を作成し、各キーに分岐を設定します。

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 を作成する必要がなくなるため、lambda 関数を簡素化できます。これには、entryProvider ビルダー関数を使用します。また、キーが見つからない場合のデフォルトのフォールバック動作(エラーをスローする)も含まれます。

entryProvider = entryProvider {
    entry<ProductList> { Text("Product List") }
    entry<ProductDetail>(
        metadata = mapOf("extraDataKey" to "extraDataValue")
    ) { key -> Text("Product ${key.id} ") }
}

このスニペットから次の点に注意してください。

  • entry は、指定されたタイプとコンポーザブル コンテンツを持つ NavEntry を定義するために使用されます。
  • entrymetadata パラメータを受け取って NavEntry.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 つのデスティネーションあり)。

すべてを組み合わせる

次の図は、ナビゲーション 3 のさまざまなオブジェクト間でのデータの流れを示しています。

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

  2. バックスタックの状態の変化がコンテンツの取得をトリガーするNavDisplay(バックスタックをレンダリングするコンポーザブル)はバックスタックをオブザーバブルにします。デフォルト設定では、最上位のバックスタック エントリが単一ペイン レイアウトで表示されます。バックスタックの最上位キーが変更されると、NavDisplay はこのキーを使用して、エントリ プロバイダから対応するコンテンツをリクエストします。

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

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