StateStore Część Androida Jetpack.
StateStore to bezpieczny pod względem typów kontener, który przechowuje i udostępnia stan aplikacji jako State w całej aplikacji.
Przypadki użycia
Tworząc obiekt StateStore o zakresie aplikacji, możesz udostępniać stan między komponentami aplikacji w ramach tego samego procesu:
- Wiele aktywności: jeśli aplikacja wyświetla jednocześnie wiele aktywności (np. aktywność skrzynki odbiorczej i aktywność edytora w kliencie poczty e-mail) lub ma aktywność, która obsługuje tryb podzielony (np. aktywność przeglądarki dokumentów), możesz użyć
StateStoredo udostępniania informacji między nimi. - Aktywność i usługa: synchronizuj stan między aktywnością a
CarAppServicelub AppFunction w tle. - Aktywność i mechanizm dostarczania treści lub widżet: udostępnianie stanu między aktywnością a widżetem na ekranie głównym.
Jeśli singleton potrzebuje tylko obiektu State<T> w pamięci bez wyszukiwania z kluczem lub przywracania śmierci procesu z obsługą DataStore, możesz zamiast tego udostępnić właściwości mutableStateOf bezpośrednio w singletonie. Porównanie interfejsów API stanu w aplikacji znajdziesz w artykule Wybieranie odpowiedniego interfejsu API stanu w architekturze.
Przegląd interfejsów API
Pakiet androidx.appstate.statestore udostępnia klasy StateStore i 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,
)
Zdefiniuj stan
Aby zapewnić bezpieczeństwo typów podczas przechowywania i pobierania wartości, StateStore używa klasy StateStoreKey do definiowania kluczy używanych do odczytu i zapisu wartości. Definicje StateStoreKey private lub internal zachowaj w menedżerze, repozytorium lub module, który jest właścicielem tego fragmentu stanu:
@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)
Tworzenie i wstrzykiwanie instancji StateStore
StateStore to zwykła klasa, której instancję tworzysz raz w zakresie aplikacji lub modułu (np. w klasie Application lub module wstrzykiwania zależności) i wstrzykujesz do menedżerów lub repozytoriów, które odczytują i zapisują stan, zamiast udostępniać StateStore bezpośrednio klasom interfejsu:
// 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, )
Stan odczytu i zapisu
W menedżerze lub repozytorium odczytuj, zapisuj i aktualizuj wartości we wstrzykniętym obiekcie StateStore, używając prywatnych definicji StateStoreKey, i udostępniaj wywołującym właściwości State<T> tylko do odczytu oraz funkcje mutacji.
Stan przeczytano / nie przeczytano
Użyj getState(stateKey, defaultValue), aby pobrać obiekt State<T>. Jeśli klucz nie został jeszcze zainicjowany, StateStore wypełnia go parametrem defaultValue, który domyślnie ma wartość stateKey.defaultValue (możesz też przekazać niestandardowy parametr defaultValue podczas inicjowania niewypełnionego klucza). Ponieważ getState zwraca State<T>, modele widoku, elementy interfejsu, widżety i bloki transform mogą odczytywać właściwości menedżera w sposób reaktywny:
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}") } }
Stan zapisu
Użyj setState(stateKey, value) w menedżerze, aby zainicjować lub zastąpić wartość powiązaną z kluczem:
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) } }
Aktualizacja stanu
Jeśli chcesz zaktualizować stan na podstawie jego bieżącej wartości, użyj updateState.
Pobiera bieżącą wartość (lub używa domyślnej, jeśli nie jest ustawiona) i stosuje funkcję lambda aktualizacji:
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 } } } }
Integracje
StateStore integruje się z Jetpack DataStore, aby zachowywać i przywracać stan po śmierci procesu i ponownym uruchomieniu aplikacji.
DataStore
Artefakt androidx.appstate.statestore:statestore-datastore umożliwia jednorazowe skonfigurowanie trwałości w udostępnionej instancji StateStore, dzięki czemu klucze trwałe dowolnego menedżera są automatycznie zapisywane i przywracane z dysku za pomocą magazynu danych.
Pakiet androidx.appstate.datastore udostępnia te interfejsy 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>,
)
- Zaimplementuj interfejs
PersistToDataStore: oznacz wszystkie obiektyStateStoreKey, które powinny być utrwalane, implementując interfejsPersistToDataStore:
@Serializable private data object FocusSessionKey : StateStoreKey<FocusSession>(defaultValue = FocusSession.Idle), PersistToDataStore
- Powiąż
StateStorez DataStore: wywołajsyncToDataStoreraz podczas konfigurowania udostępnionej instancjiStateStore(np. w klasieApplicationlub module wstrzykiwania zależności).syncToDataStoreto funkcjasuspend, która zawiesza się na czas nieokreślony, aby przywrócić zapisane wartości z dysku i kontynuować zapisywanie przyszłych zmian w podanymcoroutineScope. Wywołaj ją wewnątrzlaunchi przekażCoroutineScopena poziomie aplikacji. Po uruchomieniugetStatepoczątkowo zwracadefaultValuew pamięci i aktualizujeState, gdy DataStore zakończy odczyt z dysku:
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) }
Wytyczne dotyczące trwałości DataStore
syncToDataStore serializuje i zapisuje dane na dysku za każdym razem, gdy zmienia się wartość klucza PersistToDataStore. Dlatego podczas wybierania danych do utrwalenia i momentu ich utrwalenia postępuj zgodnie z tymi wskazówkami:
- Zapisuj sygnatury czasowe rozpoczęcia lub zakończenia zamiast każdego tyknięcia licznika: w przypadku liczników czasu lub postępu odtwarzania zachowaj stabilną wartość referencyjną (np.
endTimeMillislubstartedAtMillis) zamiast aktualizować kluczPersistToDataStoreco sekundę lub klatkę i obliczać czas, który upłynął, lub pozostały czas wyświetlania w interfejsie. - Przed zaktualizowaniem utrwalonego klucza odczekaj, aż użytkownik przestanie szybko wprowadzać dane: w przypadku wersji roboczych tekstu, zapytań lub suwaków przechowuj dane wejściowe w pamięci
MutableState(lubStateStoreKey, który nie implementujePersistToDataStore) i wywołujsetStatena utrwalonymStateStoreKeydopiero po zakończeniu wprowadzania danych. - Klucze trwałe powinny być lekkie: wszystkie klucze
PersistToDataStorewStateStoresą serializowane razem w jednym pliku przy każdej aktualizacji. Utrzymuj kompaktowy stan sesji, taki jak aktywne identyfikatory, wybrane filtry czy wersje robocze formularzy w trakcie wypełniania, a duże kolekcje lub możliwe do przeszukiwania zbiory danych przechowuj w Room.