Как сохранять состояние навигации и управлять им

В следующих разделах описаны стратегии сохранения стека возврата и хранения состояния, связанного с записями в стеке возврата.

Как сохранить историю переходов назад

Чтобы пользователям было удобно работать с приложением, важно, чтобы состояние навигации сохранялось при различных событиях жизненного цикла, в том числе при изменении конфигурации и завершении процесса. В Navigation 3 вы сами управляете стеком возврата, поэтому строгих правил по его созданию и сохранению нет. Однако в Navigation 3 есть удобный метод, который позволяет сохранить стек возврата: rememberNavBackStack.

Назначить rememberNavBackStack

rememberNavBackStack composable-функция предназначена для создания обратного стека, который сохраняется при изменении конфигурации и завершении процесса.

Чтобы кнопка rememberNavBackStack работала правильно, каждый ключ в стеке возврата должен соответствовать определенным требованиям:

  • Реализуйте интерфейс NavKey. Каждый ключ в стеке возврата должен реализовывать интерфейс NavKey. Это интерфейс-маркер, который сообщает библиотеке, что ключ можно сохранить.
  • Используйте аннотацию @Serializable. Помимо реализации NavKey, ключевые классы и объекты должны быть отмечены аннотацией @Serializable.

Ниже приведен фрагмент кода с правильной реализацией метода rememberNavBackStack:

@Serializable
data object Home : NavKey

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

Запомнить историю переходов с подтипами NavKey

Composable-функция 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 используются для сохранения состояния, связанного с интерфейсом, при изменении конфигурации, например при повороте экрана. По умолчанию ViewModels относятся к ближайшему ViewModelStoreOwner, обычно к Activity или Fragment.

Однако иногда нужно ограничить область действия ViewModel определенным NavEntry (например, экраном или пунктом назначения) в стеке переходов назад, а не всем Activity. Это гарантирует, что состояние ViewModel сохраняется только тогда, когда определенный элемент NavEntry находится в стеке возврата, и сбрасывается, когда элемент NavEntry удаляется из стека.

В библиотеке дополнений androidx.lifecycle:lifecycle-viewmodel-navigation3 есть NavEntryDecorator, которое упрощает эту задачу. Этот декоратор предоставляет ViewModelStoreOwner для каждого NavEntry. Когда вы создаете ViewModel в контенте NavEntry (например, с помощью viewModel() в Compose), оно автоматически привязывается к ключу этого NavEntry в стеке переходов. Это означает, что ViewModel создается, когда NavEntry добавляется в стек возврата, и удаляется, когда оно из него извлекается.

Чтобы использовать NavEntryDecorator для определения области действия ViewModel в NavEntry, выполните следующие действия:

  1. Добавьте зависимость androidx.lifecycle:lifecycle-viewmodel-navigation3 в файл app/build.gradle.kts.
  2. Добавьте значение по умолчанию rememberSaveableStateHolderNavEntryDecorator() в список entryDecorators при создании NavDisplay.
  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 { },
)