В зависимости от того, куда переносится состояние и какая логика требуется, вы можете использовать разные API для сохранения и восстановления состояния интерфейса. Каждое приложение использует сочетание API, чтобы достичь этой цели.
Любое приложение для Android может потерять состояние интерфейса из-за воссоздания процесса или действий. Потеря состояния может произойти по следующим причинам:
- Изменения конфигурации. Если изменение конфигурации не обрабатывается вручную, действие уничтожается и создается заново.
- Завершение процесса системой. Приложение работает в фоновом режиме, и устройство освобождает ресурсы (например, память), чтобы их могли использовать другие процессы.
Сохранение состояния после этих событий необходимо для удобства пользователей. Выбор того, какое состояние сохранять, зависит от особенностей приложения. Рекомендуется сохранять хотя бы введенные пользователем данные и состояние, связанное с навигацией. Например, это может быть позиция прокрутки списка, идентификатор элемента, о котором пользователь хочет узнать больше, выбранные настройки или текст, введенный в текстовые поля.
На этой странице описаны API, которые можно использовать для хранения состояния интерфейса в зависимости от того, где оно хранится и какая логика его использует.
Логика интерфейса
Если состояние вашего приложения хранится в интерфейсе, например в функциях, поддерживающих композицию, или в классах, хранящих состояние и относящихся к композиции, вы можете использовать rememberSaveable, чтобы сохранить состояние при воссоздании активности и процесса.
В приведенном ниже фрагменте кода rememberSaveable используется для хранения статуса одного логического элемента интерфейса:
@Composable fun ChatBubble( message: Message ) { var showDetails by rememberSaveable { mutableStateOf(false) } ClickableText( text = AnnotatedString(message.content), onClick = { showDetails = !showDetails } ) if (showDetails) { Text(message.timestamp) } }
showDetails – это логическая переменная, которая определяет, свернут или развернут значок чата.
rememberSaveable сохраняет состояние элемента интерфейса в Bundle с помощью механизма сохранения состояния экземпляра.
Он может автоматически сохранять в пакеты примитивные типы. Если состояние хранится в типе, который не является примитивным, например в классе данных, можно использовать разные механизмы сохранения, например аннотацию Parcelize, API Compose, такие как listSaver и mapSaver, или собственный класс сохранения, расширяющий класс Saver среды выполнения Compose. Подробнее о способах хранения состояния…
В приведенном ниже фрагменте кода API Compose rememberLazyListState сохраняет LazyListState, который состоит из состояния прокрутки LazyColumn или LazyRow, с помощью rememberSaveable. В нем используется LazyListState.Saver – специальная заставка, которая может сохранять и восстанавливать состояние прокрутки. После воссоздания процесса или действия (например, после изменения конфигурации, такого как ориентация устройства) состояние прокрутки сохраняется.
@Composable fun rememberLazyListState( initialFirstVisibleItemIndex: Int = 0, initialFirstVisibleItemScrollOffset: Int = 0 ): LazyListState { return rememberSaveable(saver = LazyListState.Saver) { LazyListState( initialFirstVisibleItemIndex, initialFirstVisibleItemScrollOffset ) } }
Метод
rememberSaveable использует Bundle для хранения состояния интерфейса, которое также используется другими API, записывающими в него данные, например вызовами onSaveInstanceState() в вашем действии. Однако размер этого Bundle ограничен, и хранение больших объектов может привести к исключениям TransactionTooLarge во время выполнения. Это может быть особенно проблематично в одностраничных приложениях, где один и тот же Activity используется во всем приложении.Bundle
Чтобы избежать сбоев такого типа, не храните в пакете большие сложные объекты или списки объектов.
Вместо этого сохраняйте минимально необходимый объем данных, например идентификаторы или ключи, и используйте их, чтобы делегировать восстановление более сложного состояния интерфейса другим механизмам, таким как постоянное хранилище.
Выбор дизайна зависит от того, как пользователи будут взаимодействовать с приложением.
Как проверить восстановление состояния
Вы можете проверить, правильно ли восстанавливается состояние, сохраненное с помощью rememberSaveable в элементах Compose, при воссоздании действия или процесса. Для этого используются специальные API, например StateRestorationTester. Подробнее о тестировании…
Бизнес-логика
Если состояние элемента интерфейса перенесено в ViewModel, поскольку этого требует бизнес-логика, вы можете использовать API ViewModel.
Одно из главных преимуществ использования ViewModel в приложении для Android заключается в том, что он бесплатно обрабатывает изменения конфигурации. Когда конфигурация меняется, объект activity уничтожается и создается заново, состояние интерфейса, переданное в ViewModel, сохраняется в памяти. После воссоздания старый экземпляр ViewModel прикрепляется к новому экземпляру действия.
Однако экземпляр ViewModel не переживет завершение процесса, инициированное системой.
Чтобы состояние интерфейса сохранялось, используйте модуль сохраненного состояния для ViewModel, который содержит API SavedStateHandle.
Метод
SavedStateHandle также использует механизм Bundle для хранения состояния интерфейса, поэтому его следует использовать только для хранения простого состояния элемента интерфейса.
Состояние интерфейса экрана, которое создается путем применения бизнес-правил и доступа к слоям приложения, отличным от интерфейса, не следует хранить в SavedStateHandle из-за его потенциальной сложности и размера. Для хранения сложных или больших объемов данных можно использовать разные механизмы, например локальное постоянное хранилище. После воссоздания процесса экран воссоздается с восстановленным временным состоянием, которое хранилось в SavedStateHandle (если оно было), а состояние интерфейса экрана снова создается на основе слоя данных.
API: SavedStateHandle
В SavedStateHandle есть разные API для хранения состояния элементов интерфейса, в частности:
Создать State |
saveable() |
|---|---|
StateFlow |
getStateFlow() |
Создать письмо State
Используйте saveable API из SavedStateHandle, чтобы читать и записывать состояние элементов интерфейса как MutableState. Это позволит сохранить состояние при воссоздании объектов activity и процессов с минимальным количеством кода.
API saveable поддерживает примитивные типы "из коробки" и получает параметр stateSaver для использования пользовательских сохраняющих функций, как и rememberSaveable().
В приведенном ниже фрагменте кода переменная message хранит данные, введенные пользователем в элемент TextField:
class ConversationViewModel( savedStateHandle: SavedStateHandle ) : ViewModel() { var message by savedStateHandle.saveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue("")) } private set fun update(newMessage: TextFieldValue) { message = newMessage } /*...*/ } val viewModel = ConversationViewModel(SavedStateHandle()) @Composable fun UserInput(/*...*/) { TextField( value = viewModel.message, onValueChange = { viewModel.update(it) } ) }
Дополнительную информацию об использовании API saveable можно найти в документации по SavedStateHandle.
StateFlow
Используйте getStateFlow(), чтобы хранить состояние элемента интерфейса и использовать его как поток из SavedStateHandle. StateFlow доступен только для чтения, и API требует указать ключ, чтобы вы могли заменить поток для создания нового значения. С помощью настроенного ключа можно получить значение StateFlow и собрать последние данные.
В приведенном ниже фрагменте кода savedFilterType – это переменная StateFlow, в которой хранится тип фильтра, примененный к списку каналов чата в приложении для обмена сообщениями:
private const val CHANNEL_FILTER_SAVED_STATE_KEY = "ChannelFilterKey" class ChannelViewModel( channelsRepository: ChannelsRepository, private val savedStateHandle: SavedStateHandle ) : ViewModel() { private val savedFilterType: StateFlow<ChannelsFilterType> = savedStateHandle.getStateFlow( key = CHANNEL_FILTER_SAVED_STATE_KEY, initialValue = ChannelsFilterType.ALL_CHANNELS ) private val filteredChannels: Flow<List<Channel>> = combine(channelsRepository.getAll(), savedFilterType) { channels, type -> filter(channels, type) }.onStart { emit(emptyList()) } fun setFiltering(requestType: ChannelsFilterType) { savedStateHandle[CHANNEL_FILTER_SAVED_STATE_KEY] = requestType } /*...*/ } enum class ChannelsFilterType { ALL_CHANNELS, RECENT_CHANNELS, ARCHIVED_CHANNELS }
Каждый раз, когда пользователь выбирает новый тип фильтра, вызывается функция setFiltering. Это сохранит новое значение в SavedStateHandle, связанное с ключом _CHANNEL_FILTER_SAVED_STATE_KEY_. savedFilterType – это поток, который передает последнее значение, сохраненное в ключе. filteredChannels подписан на поток, чтобы фильтровать каналы.
Дополнительную информацию об API getStateFlow() можно найти в документации по SavedStateHandle.
Сводка
В таблице ниже перечислены API, рассматриваемые в этом разделе, и указано, когда следует использовать каждый из них для сохранения состояния интерфейса.
| Событие | Логика интерфейса | Бизнес-логика в ViewModel |
|---|---|---|
| Изменения конфигурации | rememberSaveable |
Автоматически |
| Завершение процесса системой | rememberSaveable |
SavedStateHandle |
Выбор API зависит от того, где хранится состояние и какая логика требуется. Для состояния, которое используется в логике интерфейса, используйте rememberSaveable. Если состояние используется в бизнес-логике и хранится в ViewModel, сохраните его с помощью SavedStateHandle.
Для хранения небольших объемов данных о состоянии интерфейса следует использовать API пакетов (rememberSaveable и SavedStateHandle). Эти данные необходимы для восстановления интерфейса в прежнем виде с помощью других механизмов хранения. Например, если вы сохраняете в пакете идентификатор профиля, который просматривал пользователь, вы можете извлечь из уровня данных большие объемы информации, например сведения о профиле.
Чтобы узнать больше о различных способах сохранения состояния интерфейса, ознакомьтесь с документацией по сохранению состояния интерфейса и страницей Уровень данных в руководстве по архитектуре.
Рекомендуем
- Примечание. Текст ссылки показывается, когда JavaScript отключен.
- Где поднимать флаг
- Состояние и Jetpack Compose
- Списки и сетки