ナビゲーションとバックスタック

NavController が「バックスタック」を保持するユーザーがクリックすると、 確認できます。ユーザーがアプリ内で画面移動すると、NavController は、バックスタックとの間でデスティネーションを追加または削除します。

スタックであるバックスタックは「後入れ先出し」学びました。「 そのため、NavController はアイテムを一番上にプッシュし、上部からアイテムをポップします。 説明します。

基本的な動作

バックスタックの動作については、次の点に注意してください。

  • 最初のデスティネーション: ユーザーがアプリを開くと、NavController は最初のデスティネーションをバックスタックの一番上にプッシュします。
  • スタックへのプッシュ: NavController.navigate() が呼び出されるたびに、特定のデスティネーションがスタックの一番上にプッシュされます。
  • 最上部のデスティネーションをポップする: [上へ] または [戻る] をタップすると、NavController.navigateUp() メソッドと NavController.popBackStack() メソッドがそれぞれ呼び出され、スタックの最上部にあるデスティネーションがポップされます。[上へ] と [戻る] の違いについて詳しくは、ナビゲーションの原則のページをご覧ください。

ポップバック

NavController.popBackStack() メソッドでは、バックスタックから現在のデスティネーションがポップされ、前のデスティネーションへの移動が行われます。これにより、ユーザーは実質的にナビゲーション履歴内の 1 つ前のステップに戻ることができます。このメソッドでは、正常にポップされてデスティネーションに戻ったかどうかを示すブール値が返されます。

特定のデスティネーションにポップバックする

popBackStack() を使用して特定のデスティネーションに移動することもできます。これを行うには、そのオーバーロードの一つを使用します。整数の id や文字列の route など、識別子を渡すことができるものがいくつかあります。これらのオーバーロードにより、指定された識別子に関連付けられたデスティネーションにユーザーを誘導します。重要なのは、スタック内のそのデスティネーションの上にあるすべてをポップすることです。

これらのオーバーロードは、inclusive ブール値も受け取ります。これは、NavController が、指定されたデスティネーションに移動した後に、そのデスティネーションをバックスタックからポップするかどうかを決定します。

次の簡単なスニペットを例にとりましょう。

navController.popBackStack(R.id.destinationId, true)

ここでは、NavController により、整数 ID destinationId のデスティネーションにポップバックします。inclusive 引数の値が true であるため、NavController は指定されたデスティネーションもバックスタックからポップします。

失敗したポップバックを処理する

popBackStack()false を返すと、その後の NavController.getCurrentDestination() の呼び出しでは null が返ります。これは、アプリがバックスタックから最後のデスティネーションをポップしたことを意味します。この場合 ユーザーには 空白の画面にのみ移動できます。

これは、次のような場合に発生することがあります。

  • popBackStack() がスタックから何もポップしませんでした。
  • popBackStack() がバックスタックからデスティネーションをポップし、スタックが空になりました。

この問題を解決するには、新しいデスティネーションに移動するか、アクティビティで finish() を呼び出して終了する必要があります。この手順を行うスニペットは以下のとおりです。

Kotlin

...

if (!navController.popBackStack()) {
    // Call finish() on your Activity
    finish()
}

Java

...

if (!navController.popBackStack()) {
    // Call finish() on your Activity
    finish();
}

デスティネーションにポップアップする

あるデスティネーションから別のデスティネーションに移動するときにバックスタックからデスティネーションを削除するには、関連する navigate() 関数呼び出しに popUpTo() 引数を追加します。popUpTo() は、navigate() の呼び出しの一部として、バックスタックから一部のデスティネーションを削除するよう Navigation ライブラリに指示します。パラメータ値は、バックスタック上のデスティネーションの識別子です。識別子は、整数 id または文字列 route のいずれかです。

inclusive パラメータの引数に true を指定して、popUpTo() で指定したデスティネーションもバックスタックからポップするように指定することもできます。

これをプログラムで実装するには、inclusivetrue に設定した NavOptions の一部として、popUpTo()navigate() に渡します。これは Compose とビューの両方で機能します。

ポップアップ時に状態を保存する

popUpTo を使用してデスティネーションに移動する場合、必要に応じて バックスタックからポップされたすべてのデスティネーションの状態を表します。Google Chat では 次に、そのデスティネーションに移動したときに、バックスタックとデスティネーションを復元します。 できます。これにより、特定のデスティネーションの状態を保持し、 複数のバックスタックを使用できます。

これをプログラムで行うには、popUpTo をプログラムに追加するときに、saveState = true を指定します。 ナビゲーション オプションが表示されます。

ナビゲーション オプションで restoreState = true を指定して、 関連する状態が自動的に復元され、バックスタックが あります。

例:

navController.navigate(
    route = route,
    navOptions =  navOptions {
        popUpTo<A>{ saveState = true }
        restoreState = true
    }
)

XML で状態の保存と復元を有効にするには、popUpToSaveStatetrue として定義します。 関連付けられた action で、restoreState をそれぞれ true として使用します。

XML の例

action を使用した XML の popUpTo の例を次に示します。

<action
  android:id="@+id/action_a_to_b"
  app:destination="@id/b"
  app:popUpTo="@+id/a"
  app:popUpToInclusive="true"
  app:restoreState=”true”
  app:popUpToSaveState="true"/>

Compose の例

Compose での同じものの例を以下に示します。

@Composable
fun MyAppNavHost(
    modifier: Modifier = Modifier,
    navController: NavHostController = rememberNavController(),
    startDestination: Any = A
) {
    NavHost(
        modifier = modifier,
        navController = navController,
        startDestination = startDestination
    ) {
        composable<A> {
            DestinationA(
                onNavigateToB = {
                // Pop everything up to, and including, the A destination off
                // the back stack, saving the back stack and the state of its
                // destinations.
                // Then restore any previous back stack state associated with
                // the B destination.
                // Finally navigate to the B destination.
                    navController.navigate(route = B) {
                        popUpTo<A> {
                            inclusive = true
                            saveState = true
                        }
                        restoreState = true
                    }
                },
            )
        }
        composable<B> { DestinationB(/* ... */) }
    }
}

@Composable
fun DestinationA(onNavigateToB: () -> Unit) {
    Button(onClick = onNavigateToB) {
        Text("Go to A")
    }
}

より細かい設定として、次の方法で NavController.navigate() の呼び出し方法を変更できます。

// Pop everything up to the destination_a destination off the back stack before
// navigating to the "destination_b" destination
navController.navigate("destination_b") {
    popUpTo("destination_a")
}

// Pop everything up to and including the "destination_a" destination off
// the back stack before navigating to the "destination_b" destination
navController.navigate("destination_b") {
    popUpTo("destination_a") { inclusive = true }
}

// Navigate to the "search” destination only if we’re not already on
// the "search" destination, avoiding multiple copies on the top of the
// back stack
navController.navigate("search") {
    launchSingleTop = true
}

NavController.navigate() にオプションを渡す方法については、オプションでの移動ガイドをご覧ください。

アクションを使用してポップする

アクションを使用して移動する場合、必要に応じて、追加のデスティネーションをバックスタックからポップできます。たとえば、アプリに初期ログインフローがある場合、一度ユーザーがログインした後は、[戻る] ボタンをタップしてもログインフローに戻ることがないように、ログイン関連デスティネーションをすべてバックスタックからポップする必要があります。

その他の情報

詳しくは、以下のページをご覧ください。

  • 循環型ナビゲーション: ナビゲーション フローが循環型の場合に、バックスタックが過剰にならないようにする方法を説明します。
  • ダイアログ デスティネーション: ダイアログ デスティネーションにより、バックスタックの管理方法に独自の考慮事項が含まれるようになる点について説明します。