StateStore Teil von Android Jetpack.
„StateStore“ ist ein typsicherer Container, der den freigegebenen Anwendungsstatus als Compose-State in Ihrer App speichert und verfügbar macht.
Anwendungsfälle
Wenn Sie einen StateStore mit Anwendungsbereich erstellen, können Sie den Status zwischen Anwendungskomponenten innerhalb desselben Prozesses teilen:
- Mehrere Aktivitäten: Wenn in deiner App mehrere Aktivitäten gleichzeitig angezeigt werden (z. B. eine Posteingangsaktivität und eine Editoraktivität in einem E-Mail-Client) oder eine Aktivität vorhanden ist, die Multi-Instanz-Modus unterstützt (z. B. eine Dokumentanzeigeaktivität), kannst du
StateStoreverwenden, um Informationen zwischen ihnen zu teilen. - Aktivität und Dienst: Synchronisieren Sie den Status zwischen einer Aktivität und einem Hintergrund-
CarAppServiceoder einer AppFunction. - Aktivität und Contentanbieter oder Widget: Status zwischen einer Aktivität und einem Startbildschirm-Widget teilen.
Wenn ein Singleton nur State<T> im Arbeitsspeicher benötigt, ohne dass eine schlüsselbasierte Suche oder eine DataStore-gestützte Wiederherstellung nach einer Prozessbeendigung erforderlich ist, können Sie stattdessen mutableStateOf-Properties direkt im Singleton verfügbar machen. Einen Vergleich der Status-APIs in Ihrer App finden Sie unter Die richtige Status-API für Ihre Architektur auswählen.
API-Übersicht
Das androidx.appstate.statestore-Paket bietet die Klassen StateStore und 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,
)
Bundesland angeben
Um die Typsicherheit beim Speichern und Abrufen von Werten zu gewährleisten, verwendet StateStore die Klasse StateStoreKey, um die Schlüssel zum Lesen und Schreiben von Werten zu definieren. Beschränken Sie StateStoreKey-Definitionen (private oder internal) auf den Manager, das Repository oder das Modul, das diesen Teil des Status besitzt:
@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-Instanzen erstellen und einfügen
StateStore ist eine reguläre Klasse, die Sie einmal im Anwendungs- oder Modulbereich instanziieren (z. B. in Ihrer Application-Klasse oder einem Dependency-Injection-Modul) und in die Manager oder Repositories einfügen, die den Status lesen und schreiben, anstatt StateStore direkt für UI-Klassen freizugeben:
// 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, )
Lese- und Schreibstatus
In Ihrem Manager oder Repository können Sie Werte in der eingefügten StateStore mit Ihren privaten StateStoreKey-Definitionen lesen, schreiben und aktualisieren und Lesezugriff-State<T>-Eigenschaften und Mutationsfunktionen für Aufrufer verfügbar machen.
Lesestatus
Verwenden Sie getState(stateKey, defaultValue), um ein State<T>-Objekt abzurufen. Wenn der Schlüssel noch nicht initialisiert ist, wird er von StateStore mit dem Parameter defaultValue gefüllt, der standardmäßig auf stateKey.defaultValue gesetzt ist. Sie können aber auch einen benutzerdefinierten defaultValue übergeben, wenn Sie einen nicht gefüllten Schlüssel initialisieren. Da getState ein State<T> zurückgibt, können ViewModels, UI-Kompositionen, Widgets und transform-Blöcke die Property des Managers reaktiv lesen:
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}") } }
Schreibstatus
Verwenden Sie setState(stateKey, value) in Ihrem Manager, um den mit einem Schlüssel verknüpften Wert zu initialisieren oder zu ersetzen:
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) } }
Status aktualisieren
Wenn Sie einen Status basierend auf seinem aktuellen Wert aktualisieren müssen, verwenden Sie updateState.
Dadurch wird der aktuelle Wert abgerufen (oder der Standardwert verwendet, falls nicht festgelegt) und die Update-Lambda-Funktion angewendet:
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 } } } }
Integrationen
StateStore lässt sich in Jetpack DataStore einbinden, um den Status bei Prozessbeendigung und App-Neustarts beizubehalten und wiederherzustellen.
Datastore
Mit dem Artefakt androidx.appstate.statestore:statestore-datastore können Sie die Persistenz einmalig auf Ihrer freigegebenen StateStore-Instanz konfigurieren, sodass die persistenten Schlüssel aller Manager automatisch mit DataStore auf dem Laufwerk gespeichert und von dort wiederhergestellt werden.
Das androidx.appstate.datastore-Paket bietet folgende APIs:
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>,
)
PersistToDataStoreimplementieren: Markieren Sie alleStateStoreKey, die beibehalten werden sollen, indem Sie die SchnittstellePersistToDataStoreimplementieren:
@Serializable private data object FocusSessionKey : StateStoreKey<FocusSession>(defaultValue = FocusSession.Idle), PersistToDataStore
StateStorean DataStore binden: Rufen SiesyncToDataStoreeinmal auf, wenn Sie Ihre freigegebeneStateStore-Instanz einrichten (z. B. in IhrerApplication-Klasse oder Ihrem Dependency-Injection-Modul). DasyncToDataStoreeinesuspend-Funktion ist, die unbegrenzt angehalten wird, um gespeicherte Werte vom Laufwerk wiederherzustellen und zukünftige Änderungen im angegebenencoroutineScopezu speichern, rufen Sie sie innerhalb vonlaunchauf und übergeben Sie IhreCoroutineScopeauf Anwendungsebene. Beim Start gibtgetStatezuerstdefaultValueim Arbeitsspeicher zurück und aktualisiertState, sobald DataStore das Lesen vom Laufwerk abgeschlossen hat:
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) }
Richtlinien für die DataStore-Persistenz
Da syncToDataStore serialisiert und auf das Laufwerk schreibt, wenn sich der Wert eines PersistToDataStore-Schlüssels ändert, sollten Sie die folgenden Richtlinien beachten, wenn Sie entscheiden, was und wann Sie persistent speichern möchten:
- Start- oder Endzeitstempel anstelle jedes Timer-Ticks speichern: Speichere für Timer oder den Wiedergabefortschritt einen stabilen Referenzwert (z. B.
endTimeMillisoderstartedAtMillis), anstatt einenPersistToDataStore-Schlüssel jede Sekunde oder jeden Frame zu aktualisieren, und berechne die verstrichene oder verbleibende Zeit in der Benutzeroberfläche. - Schnelle Nutzereingaben vor dem Aktualisieren eines beibehaltenen Schlüssels entprellen: Bei Textentwürfen, Suchanfragen oder Schiebereglern solltest du die unmittelbare Eingabe in einem speicherinternen
MutableState(oder einemStateStoreKey, derPersistToDataStorenicht implementiert) speichern undsetStateerst dann für den beibehaltenenStateStoreKeyaufrufen, wenn die Eingabe abgeschlossen ist. - Persistente Schlüssel klein halten: Alle
PersistToDataStore-Schlüssel in einemStateStorewerden bei jeder Aktualisierung zusammen in einer einzigen Datei serialisiert. Speichern Sie kompakte Sitzungsstatus wie aktive IDs, Filterauswahlen oder laufende Formular-Entwürfe und speichern Sie große Sammlungen oder abfragbare Datasets in Room.