StateStore Часть Android Jetpack.
StateStore – это типобезопасный контейнер, который хранит и предоставляет общее состояние приложения в виде State Compose.
Примеры использования
Создав StateStore в области действия приложения, вы можете передавать состояние между компонентами приложения в рамках одного процесса:
- Несколько действий. Если в приложении одновременно выполняется несколько действий (например, в почтовом клиенте открыты папка "Входящие" и редактор писем) или действие поддерживает многооконный режим (например, в средстве просмотра документов), вы можете использовать
StateStoreдля обмена информацией между ними. - Действие и сервис. Синхронизация состояния между действием и фоновым сервисом
CarAppServiceили AppFunction. - Activity и поставщик контента или виджет. Обмен данными между Activity и виджетом на главном экране.
Если синглтону требуется только State<T> в памяти без поиска по ключу или восстановления после завершения процесса с поддержкой DataStore, вы можете вместо этого предоставить свойства mutableStateOf непосредственно в синглтоне. Чтобы сравнить API состояний в приложении, прочитайте статью Как выбрать подходящий API состояний для архитектуры приложения.
Обзор API
Пакет androidx.appstate.statestore содержит классы StateStore и StateStoreKey:
package androidx.appstate.statestore
import androidx.compose.runtime.State
import kotlinx.serialization.Serializable
public class StateStore {
public val keys: Set<StateStoreKey<*>>
public fun <T> getState(
stateKey: StateStoreKey<T>,
defaultValue: T = stateKey.defaultValue,
): State<T>
public fun <T> setState(stateKey: StateStoreKey<T>, value: T)
public inline fun <T> updateState(
stateKey: StateStoreKey<T>,
defaultValue: T = stateKey.defaultValue,
update: (T) -> T,
)
}
@Serializable
public open class StateStoreKey<T>(
public val defaultValue: T,
)
Укажите штат
Чтобы обеспечить типобезопасность при хранении и получении значений, StateStore использует класс StateStoreKey для определения ключей, используемых для чтения и записи значений. Определения StateStoreKey (private или internal) должны находиться в менеджере, репозитории или модуле, которому принадлежит этот фрагмент состояния.
@Serializable sealed interface FocusSession { @Serializable data object Idle : FocusSession @Serializable data class Running( val taskId: String, val endTimeMillis: Long, ) : FocusSession @Serializable data class Paused( val taskId: String, val remainingMillis: Long, ) : FocusSession } @Serializable private data object FocusSessionKey : StateStoreKey<FocusSession>(defaultValue = FocusSession.Idle)
Создание и внедрение экземпляров StateStore
StateStore – это обычный класс, экземпляр которого создается один раз в области приложения или модуля (например, в классе Application или модуле внедрения зависимостей) и внедряется в менеджеры или репозитории, которые считывают и записывают состояние, а не предоставляют StateStore непосредственно классам пользовательского интерфейса:
// Instantiate at the application or DI module level val stateStore = StateStore() // Inject into managers or repositories that encapsulate their own keys class FocusSessionManager( private val stateStore: StateStore, )
Чтение и запись состояния
В менеджере или репозитории считывайте, записывайте и обновляйте значения во внедренном StateStore, используя закрытые определения StateStoreKey, и предоставляйте вызывающим объектам доступ к свойствам State<T>, предназначенным только для чтения, и функциям изменения.
Статус прочтения
Используйте getState(stateKey, defaultValue), чтобы получить объект State<T>. Если ключ ещё не инициализирован, StateStore заполняет его параметром defaultValue, который по умолчанию равен stateKey.defaultValue (или вы можете передать пользовательский defaultValue при инициализации незаполненного ключа). Поскольку getState возвращает State<T>, ViewModels, UI composables, виджеты и блоки transform могут считывать свойство менеджера реактивно:
class FocusSessionManager( private val stateStore: StateStore, ) { val session: State<FocusSession> = stateStore.getState(FocusSessionKey) } class FocusTimerViewModel( focusSessionManager: FocusSessionManager, ) : ViewModel() { val session: State<FocusSession> = focusSessionManager.session } @Composable fun FocusBanner(viewModel: FocusTimerViewModel = viewModel()) { // Reads state reactively; recomposes when FocusSessionKey updates when (val session = viewModel.session.value) { FocusSession.Idle -> Text(text = "No active focus session") is FocusSession.Running -> Text(text = "Focusing on ${session.taskId}") is FocusSession.Paused -> Text(text = "Paused: ${session.taskId}") } }
Запись данных о состоянии
Используйте setState(stateKey, value) в менеджере, чтобы инициализировать или заменить значение, связанное с ключом:
class FocusSessionManager( private val stateStore: StateStore, ) { val session: State<FocusSession> = stateStore.getState(FocusSessionKey) fun startFocus(taskId: String, endTimeMillis: Long) { stateStore.setState( FocusSessionKey, FocusSession.Running( taskId = taskId, endTimeMillis = endTimeMillis, ), ) } fun endFocus() { stateStore.setState(FocusSessionKey, FocusSession.Idle) } }
Как обновить состояние
Если вам нужно обновить состояние на основе его текущего значения, используйте updateState.
Это позволяет получить текущее значение (или использовать значение по умолчанию, если оно не задано) и применить лямбда-функцию обновления:
class FocusSessionManager( private val stateStore: StateStore, ) { val session: State<FocusSession> = stateStore.getState(FocusSessionKey) fun pauseFocus(nowMillis: Long) { stateStore.updateState(FocusSessionKey) { current -> if (current is FocusSession.Running) { FocusSession.Paused( taskId = current.taskId, remainingMillis = (current.endTimeMillis - nowMillis) .coerceAtLeast(0L), ) } else { current } } } fun resumeFocus(nowMillis: Long) { stateStore.updateState(FocusSessionKey) { current -> if (current is FocusSession.Paused) { FocusSession.Running( taskId = current.taskId, endTimeMillis = nowMillis + current.remainingMillis, ) } else { current } } } }
Интеграции
StateStore интегрируется с Jetpack DataStore, чтобы сохранять и восстанавливать состояние при завершении процесса и перезапуске приложения.
DataStore
Артефакт androidx.appstate.statestore:statestore-datastore позволяет один раз настроить сохранение в общем экземпляре StateStore, чтобы сохраненные ключи любого менеджера автоматически сохранялись и восстанавливались с диска с помощью DataStore.
Пакет androidx.appstate.datastore предоставляет следующие API:
package androidx.appstate.datastore
import androidx.appstate.statestore.StateStore
import androidx.datastore.core.DataStore
import kotlinx.coroutines.CoroutineScope
public interface PersistToDataStore
public suspend fun StateStore.syncToDataStore(
path: String,
coroutineScope: CoroutineScope,
)
public suspend fun StateStore.syncToDataStore(
dataStore: DataStore<StateStorePreferences>,
)
- Реализуйте интерфейс
PersistToDataStore. Пометьте все объектыStateStoreKey, которые должны сохраняться, реализовав интерфейсPersistToDataStore.
@Serializable private data object FocusSessionKey : StateStoreKey<FocusSession>(defaultValue = FocusSession.Idle), PersistToDataStore
- Привяжите
StateStoreк DataStore. ВызовитеsyncToDataStoreодин раз при настройке общего экземпляраStateStore(например, в классеApplicationили модуле внедрения зависимостей). ПосколькуsyncToDataStore– это функцияsuspend, которая приостанавливается на неопределенный срок, чтобы восстановить сохраненные значения с диска и продолжить сохранять будущие изменения в предоставленномcoroutineScope, вызовите ее внутриlaunchи передайтеCoroutineScopeна уровне приложения. При запускеgetStateсначала возвращаетdefaultValueв памяти и обновляетState, как только DataStore завершает чтение с диска:
class ProductivityApplication : Application() { val applicationScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) val stateStore = StateStore().also { store -> // Restore saved values and automatically persist future changes // for all PersistToDataStore keys across managers applicationScope.launch { store.syncToDataStore( path = dataStoreFile("statestore.json").absolutePath, coroutineScope = applicationScope, ) } } val focusSessionManager = FocusSessionManager(stateStore) }
Рекомендации по сохранению данных в DataStore
Поскольку syncToDataStore сериализует и записывает данные на диск при каждом изменении значения ключа PersistToDataStore, следуйте этим рекомендациям при выборе того, что и когда сохранять:
- Сохраняйте временные метки начала или окончания вместо каждого тика таймера. Для таймеров или прогресса воспроизведения сохраняйте стабильное эталонное значение (например,
endTimeMillisилиstartedAtMillis), а не обновляйте ключPersistToDataStoreкаждую секунду или кадр, и вычисляйте текущее прошедшее или оставшееся время в пользовательском интерфейсе. - Устраняйте дребезг контактов при быстром вводе данных пользователем, прежде чем обновлять сохраненный ключ. Для черновиков текста, поисковых запросов или ползунков сохраняйте немедленный ввод в памяти
MutableState(илиStateStoreKey, который не реализуетPersistToDataStore) и вызывайтеsetStateдля сохраненногоStateStoreKeyтолько после того, как ввод стабилизируется. - Используйте простые ключи. Все ключи
PersistToDataStoreвStateStoreсериализуются в один файл при каждом обновлении. Сохраняйте компактное состояние сеанса, например активные идентификаторы, выбранные фильтры или черновики форм, а большие коллекции или наборы данных, доступные для запросов, храните в Room.