В зависимости от того, куда сохраняется ваше состояние и какая логика требуется, вы можете использовать различные 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 , или реализовать собственный класс сохранения, расширяющий класс Compose runtime Saver . Подробнее об этих методах см. в документации «Способы хранения состояния» .
В приведенном ниже фрагменте кода 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-приложении заключается в том, что он автоматически обрабатывает изменения конфигурации. При изменении конфигурации, уничтожении и повторном создании активности, состояние пользовательского интерфейса, перемещенное в ViewModel сохраняется в памяти. После повторного создания старый экземпляр ViewModel прикрепляется к новому экземпляру активности.
Однако экземпляр ViewModel не сохраняется после завершения процесса, инициированного системой. Чтобы состояние пользовательского интерфейса сохранилось, используйте модуль Saved State для ViewModel , который содержит API SavedStateHandle .
Передовая практика
SavedStateHandle также использует механизм Bundle для хранения состояния пользовательского интерфейса, поэтому его следует использовать только для хранения состояния простых элементов пользовательского интерфейса .
Состояние пользовательского интерфейса экрана , которое формируется путем применения бизнес-правил и доступа к уровням вашего приложения, отличным от пользовательского интерфейса, не следует хранить в SavedStateHandle из-за его потенциальной сложности и размера. Для хранения сложных или больших данных можно использовать различные механизмы, например, локальное постоянное хранилище . После перезапуска процесса экран воссоздается с восстановленным временным состоянием, которое было сохранено в SavedStateHandle (если таковое имелось), и состояние пользовательского интерфейса экрана снова формируется из уровня данных.
API SavedStateHandle
SavedStateHandle имеет различные API для хранения состояния элементов пользовательского интерфейса, наиболее примечательными из которых являются:
Составное State | saveable() |
|---|---|
StateFlow | getStateFlow() |
Составное State
Используйте API saveable для чтения и записи состояния элементов пользовательского интерфейса в виде MutableState , чтобы оно сохранялось SavedStateHandle повторном запуске активности и процесса с минимальной настройкой кода.
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 ). Эти данные — минимально необходимые для восстановления пользовательского интерфейса до его предыдущего состояния, наряду с другими механизмами хранения. Например, если вы храните в пакете идентификатор профиля, который просматривал пользователь, вы можете получать большие объемы данных, такие как сведения о профиле, из слоя данных.
Для получения более подробной информации о различных способах сохранения состояния пользовательского интерфейса см. общую документацию по сохранению состояния пользовательского интерфейса и страницу уровня данных в руководстве по архитектуре.
{% verbatim %}Рекомендуем вам
- Примечание: текст ссылки отображается, когда JavaScript отключен.
- Где поднять штат
- State and Jetpack Compose
- Списки и таблицы