В зависимости от того, куда вы поднимаете состояние и какая логика требуется, вы можете использовать разные API для хранения и восстановления состояния пользовательского интерфейса. Каждое приложение использует комбинацию API для наилучшего достижения этой цели.
Любое приложение Android может потерять состояние пользовательского интерфейса из-за повторного создания действия или процесса. Это может произойти по следующим причинам:
- Изменения конфигурации. Действие удаляется и создается заново, если изменение конфигурации не обрабатывается вручную.
- Завершение процесса, инициированное системой. Приложение работает в фоновом режиме, а устройство освобождает ресурсы (например, память) для других процессов.
Сохранение состояния после этих событий имеет важное значение для положительного пользовательского опыта. опыт. Выбор состояния, которое нужно сохранить, зависит от уникальных путей пользователя в вашем приложении. Рекомендуем сохранять как минимум данные, введенные пользователем, и состояние навигации. Примеры этого включают позицию прокрутки списка, идентификатор элемента, о котором пользователь хочет получить более подробную информацию, текущий выбор пользовательских настроек или ввод в текстовые поля.
На этой странице приведены API, доступные для хранения состояния пользовательского интерфейса, в зависимости от того, куда поднято ваше состояние и какая логика ему необходима.
Логика интерфейса
Если состояние поднято в интерфейсе (в composable-функциях или простых классах хранилища состояния, ограниченных областью видимости Composition), вы можете использовать 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. Подробнее о способах хранения состояния…
В следующем фрагменте кода Compose API rememberLazyListState сохраняет LazyListState, который состоит из состояния прокрутки LazyColumn или LazyRow, с помощью rememberSaveable. Для этого используется LazyListState.Saver – специальный объект 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 из SavedStateHandle, чтобы считывать и записывать состояние элемента интерфейса как MutableState. Это позволит сохранить его при повторном создании действия и процесса с минимальной настройкой кода.
API saveable поддерживает примитивные типы и получает параметр stateSaver для использования пользовательских объектов Saver, как и 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 отключен.
- Where to hoist state
- Состояние и Jetpack Compose
- Списки и сетки