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

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

Сохранение обратного стека

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