基本を理解して実装する

Navigation  は、ユーザーがアプリ内を移動する方法を説明します。ユーザーは通常、タップまたはクリックによって 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()
}

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

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

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

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

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

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

通常、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 を定義するために使用されます。
  • 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. 2 つの宛先がある場合の NavDisplay のデフォルトの動作。

すべてを組み合わせる

次の図は、Navigation 3 のさまざまなオブジェクト間でデータがどのように流れるかを示しています。

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

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

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

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