StateStore Parte di Android Jetpack.
StateStore è un container con sicurezza dei tipi che archivia ed espone lo stato dell'applicazione condiviso come State di Compose nell'app.
Casi d'uso
Creando un StateStore con ambito applicazione, puoi condividere lo stato tra i componenti dell'applicazione all'interno dello stesso processo:
- Più attività: se la tua app mostra più attività contemporaneamente (ad esempio un'attività di posta in arrivo e un'attività di editor in un client di posta) o ha un'attività che supporta multi-istanza (ad esempio un'attività di visualizzazione di documenti), puoi utilizzare
StateStoreper condividere informazioni tra di loro. - Attività e servizio: sincronizza lo stato tra un'attività e un
CarAppServiceo un'AppFunction in background. - Attività e provider di contenuti o widget: condividi lo stato tra un'attività e un widget della schermata Home.
Se un singleton richiede solo State<T> in memoria senza ricerca con chiave o ripristino dell'interruzione del processo supportato da DataStore, puoi esporre le proprietà mutableStateOf direttamente sul singleton. Per un confronto delle API di stato
nella tua app, consulta Scegli l'API di stato giusta per la tua
architettura.
Panoramica dell'API
Il pacchetto androidx.appstate.statestore fornisce le classi StateStore e 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,
)
Definisci lo stato
Per garantire la sicurezza dei tipi durante l'archiviazione e il recupero dei valori, StateStore utilizza la classe StateStoreKey per definire le chiavi utilizzate per leggere e scrivere i valori. Mantieni le definizioni StateStoreKey private o internal nel gestore, nel repository o nel modulo proprietario di quella sezione dello stato:
@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)
Crea e inserisci istanze StateStore
StateStore è una classe normale di cui crei un'istanza una volta nell'ambito dell'applicazione o del modulo (ad esempio, nella classe Application o in un modulo di inserimento delle dipendenze) e che inserisci nei gestori o nei repository che leggono e scrivono lo stato, anziché esporre StateStore direttamente alle classi UI:
// 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, )
Stato di lettura e scrittura
All'interno del gestore o del repository, leggi, scrivi e aggiorna i valori nel
StateStore inserito utilizzando le definizioni StateStoreKey private ed esponi
le proprietà State<T> di sola lettura e le funzioni di mutazione ai chiamanti.
Stato della lettura
Utilizza getState(stateKey, defaultValue) per recuperare un oggetto State<T>. Se la chiave non è ancora inizializzata, StateStore la compila con il parametro defaultValue, che per impostazione predefinita è stateKey.defaultValue (oppure puoi passare un defaultValue personalizzato durante l'inizializzazione di una chiave non compilata). Poiché getState restituisce un State<T>, i ViewModel, i componibili dell'interfaccia utente, i widget e i blocchi transform possono leggere la proprietà del gestore in modo reattivo:
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}") } }
Stato di scrittura
Utilizza setState(stateKey, value) all'interno del gestore per inizializzare o sostituire il valore associato a una chiave:
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) } }
Aggiorna stato
Se devi aggiornare uno stato in base al suo valore corrente, utilizza updateState.
In questo modo viene recuperato il valore corrente (o viene utilizzato quello predefinito se non è impostato) e viene applicata la funzione lambda di aggiornamento:
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 } } } }
Integrazioni
StateStore si integra con Jetpack DataStore per rendere persistente e ripristinare lo stato in caso di interruzione del processo e riavvio dell'app.
DataStore
L'artefatto androidx.appstate.statestore:statestore-datastore ti consente di configurare la persistenza una sola volta nell'istanza StateStore condivisa, in modo che le chiavi persistenti di qualsiasi gestore vengano salvate e ripristinate automaticamente dal disco utilizzando DataStore.
Il pacchetto androidx.appstate.datastore fornisce le seguenti 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>,
)
- Implementa
PersistToDataStore: contrassegna qualsiasiStateStoreKeyche deve essere mantenuto implementando l'interfacciaPersistToDataStore:
@Serializable private data object FocusSessionKey : StateStoreKey<FocusSession>(defaultValue = FocusSession.Idle), PersistToDataStore
- Associa
StateStorea DataStore: chiamasyncToDataStoreuna volta durante la configurazione dell'istanzaStateStorecondivisa (ad esempio, nella classeApplicationo nel modulo di inserimento delle dipendenze). PoichésyncToDataStoreè una funzionesuspendche si sospende a tempo indeterminato per ripristinare i valori salvati dal disco e continuare a salvare le modifiche future nelcoroutineScopefornito, chiamala all'interno dilaunche passa ilCoroutineScopea livello di applicazione. All'avvio,getStaterestituisce inizialmentedefaultValuein memoria e aggiornaStateuna volta che DataStore ha terminato la lettura dal disco:
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) }
Linee guida per la persistenza di DataStore
Poiché syncToDataStore serializza e scrive su disco ogni volta che il valore di una chiave PersistToDataStore cambia, segui queste linee guida quando scegli cosa e quando rendere persistente:
- Salva i timestamp di inizio o fine anziché ogni tick del timer: per i timer o l'avanzamento della riproduzione, mantieni un valore di riferimento stabile (ad esempio
endTimeMillisostartedAtMillis) anziché aggiornare una chiavePersistToDataStoreogni secondo o fotogramma e calcola il tempo trascorso o rimanente in tempo reale nella UI. - Elimina l'input utente rapido prima di aggiornare una chiave persistente: per le bozze di testo, le query di ricerca o i cursori, mantieni l'input immediato in un
MutableStatein memoria (o in unStateStoreKeyche non implementaPersistToDataStore) e chiamasetStatesulStateStoreKeypersistente solo dopo che l'input si è stabilizzato. - Mantieni le chiavi persistenti leggere: tutte le chiavi
PersistToDataStorein unStateStorevengono serializzate insieme in un unico file a ogni aggiornamento. Mantieni lo stato della sessione compatto, ad esempio ID attivi, selezioni di filtri o bozze di moduli in corso, e conserva raccolte di grandi dimensioni o set di dati interrogabili in Room.