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

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 を使用してデスティネーションに移動する場合、必要に応じて、バックスタックからポップされたすべてのデスティネーションの状態を保存できます。

このオプションを有効にするには、関連付けられた actionpopUpToSaveStatetrue に指定するか、NavController.navigate() を呼び出します。

デスティネーションに移動するときには、restoreSaveStatetrue に指定し、destination プロパティでそのデスティネーションに関連付けられた状態を自動的に復元できます。

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: String = "destination_a"
) {
    NavHost(
        modifier = modifier,
        navController = navController,
        startDestination = startDestination
    ) {
        composable("destination_a") {
            DestinationA(
                onNavigateToB = {
                // 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") {
                            inclusive = true
                            saveState = true
                        }
                    }
                },
            )
        }
        composable("destination_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() にオプションを渡す方法については、オプションでの移動ガイドをご覧ください。

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

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

その他の情報

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

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