ナビゲーションの状態を保存、管理する

以降のセクションでは、バックスタックを保存し、バックスタックのエントリに関連付けられた状態を保存するための戦略について説明します。

バックスタックを保存する

優れたユーザー エクスペリエンスを実現するには、構成の変更やプロセスの終了など、さまざまなライフサイクル イベントをまたいでアプリのナビゲーション状態を維持することが重要です。Navigation 3 では、バックスタックを所有しているため、作成方法や保存方法に関する厳格なガイドラインはありません。ただし、Navigation 3 には、保存可能なバックスタックを提供する便利なメソッド rememberNavBackStack が用意されています。

rememberNavBackStack を使用する

rememberNavBackStack コンポーズ可能な関数は、構成の変更やプロセスの終了後も保持されるバックスタックを作成するように設計されています。

rememberNavBackStack が正しく機能するには、バックスタック内の各キーが特定の要件を満たしている必要があります。

  • NavKey インターフェースを実装する: バックスタック内のすべてのキーは、NavKey インターフェースを実装する必要があります。これは、キーを保存できることをライブラリに通知するマーカー インターフェースとして機能します。
  • @Serializable アノテーションを付ける: NavKey を実装するだけでなく、キーのクラスとオブジェクトに @Serializable アノテーションを付ける必要があります。

次のスニペットは、rememberNavBackStack の正しい実装を示しています。

@Serializable
data object Home : NavKey

@Composable
fun NavBackStack() {
    val backStack = rememberNavBackStack(Home)
}

NavKey のサブタイプを含むバックスタックを記憶する

コンポーズ可能な関数 rememberNavBackStack は NavBackStack<NavKey> を返します。アプリが NavKey の独自のサブタイプを定義し、そのサブタイプからすべてのキーが継承される場合は、次のようにカスタムの remember 関数を実装することで、その型を保持できます。

@Serializable
sealed interface MyAppNavKey : NavKey

@Serializable
data object ScreenA: MyAppNavKey

@Serializable
data class ScreenB(val id: String): MyAppNavKey

@Composable
fun rememberMyAppNavBackStack(vararg elements: MyAppNavKey): NavBackStack<MyAppNavKey> {
    return rememberSerializable(serializer = serializer()) {
        NavBackStack(*elements)
    }
}

@Composable
fun MyApp() {
    // defaultNavBackStack is NavBackStack<NavKey>
    val defaultNavBackStack = rememberNavBackStack(ScreenA)
    // myAppNavBackStack is NavBackStack<MyAppNavKey>
    val myAppNavBackStack = rememberMyAppNavBackStack(ScreenA)
}

オープン ポリモーフィズムの処理方法など、その他の例については、NavBackStackSamples をご覧ください。

代替方法: ViewModel に保存する

バックスタックを管理するもう一つの方法は、ViewModel に保存することです。ViewModel またはその他のカスタム ストレージを使用する場合に、プロセスの終了後もデータを保持するには、次の操作を行う必要があります。

  • キーをシリアル化可能にする: rememberNavBackStack と同様に、ナビゲーション キーはシリアル化可能である必要があります。
  • シリアル化と逆シリアル化を手動で処理する: アプリがバックグラウンドに移行するとき、または復元されるときに、各キーのシリアル化された表現を永続ストレージ(SharedPreferences、データベース、ファイルなど)に手動で保存し、そこから逆シリアル化する必要があります。

ViewModel インスタンスを NavEntry オブジェクトにスコープする

ViewModels は、画面の回転などの構成変更後も UI 関連の状態を維持するために使用されます。デフォルトでは、ViewModels のスコープは最も近い ViewModelStoreOwner(通常は Activity または Fragment)に設定されます。

ただし、Activity 全体ではなく、バックスタック上の特定の NavEntry(特定の画面またはデスティネーション)に ViewModel のスコープを設定したい場合があります。これにより、ViewModel の状態は、特定の NavEntry がバックスタックの一部である間のみ保持され、NavEntry がポップされるとクリアされます。

androidx.lifecycle:lifecycle-viewmodel-navigation3 アドオン ライブラリには、これを容易にする NavEntryDecorator が用意されています。このデコレータは、NavEntry ごとに ViewModelStoreOwner を提供します。NavEntry のコンテンツ内に ViewModel を作成すると(Compose で viewModel() を使用する場合など)、バックスタック上の特定の NavEntry のキーに自動的にスコープが設定されます。つまり、ViewModel は、NavEntry がバックスタックに追加されたときに作成され、削除されたときにクリアされます。

NavEntryDecorator を使用して ViewModel を NavEntry にスコープ設定する手順は次のとおりです。

  1. androidx.lifecycle:lifecycle-viewmodel-navigation3 依存関係を app/build.gradle.kts ファイルに追加します。
  2. NavDisplay を作成するときに、デフォルトの rememberSaveableStateHolderNavEntryDecorator() を entryDecorators のリストに追加します。
  3. rememberViewModelStoreNavEntryDecorator() を entryDecorators のリストに追加します。

NavDisplay(
    entryDecorators = listOf(
        // Add the default decorators for managing scenes and saving state
        rememberSaveableStateHolderNavEntryDecorator(),
        // Then add the view model store decorator
        rememberViewModelStoreNavEntryDecorator()
    ),
    backStack = backStack,
    entryProvider = entryProvider { },
)