StateStore Fait partie d'Android Jetpack.
StateStore est un conteneur de sûreté du typage qui stocke et expose l'état de l'application partagée en tant que State Compose dans l'ensemble de votre application.
Cas d'utilisation
En créant un StateStore à l'échelle de l'application, vous pouvez partager l'état entre les composants d'application au sein du même processus :
- Activités multiples : si votre application affiche plusieurs activités simultanément (par exemple, une activité de boîte de réception et une activité d'éditeur dans un client de messagerie) ou si elle comporte une activité compatible avec les multi-instances (par exemple, une activité de visionneuse de documents), vous pouvez utiliser
StateStorepour partager des informations entre elles. - Activité et service : synchronisez l'état entre une activité et un
CarAppServiceou une AppFunction en arrière-plan. - Activité et fournisseur de contenu ou widget : partagez l'état entre une activité et un widget de l'écran d'accueil.
Si un singleton n'a besoin que de State<T> en mémoire sans recherche par clé ni restauration de l'arrêt du processus basée sur DataStore, vous pouvez exposer les propriétés mutableStateOf directement sur le singleton. Pour comparer les API d'état dans votre application, consultez Choisir l'API d'état adaptée à votre architecture.
Présentation de l'API
Le package androidx.appstate.statestore fournit les classes StateStore et 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,
)
Définir votre état
Pour garantir la sûreté du typage lors du stockage et de la récupération des valeurs, StateStore utilise la classe StateStoreKey pour définir les clés utilisées pour lire et écrire des valeurs. Conservez les définitions StateStoreKey private ou internal dans le gestionnaire, le dépôt ou le module propriétaire de cette partie de l'état :
@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)
Créer et injecter des instances StateStore
StateStore est une classe standard que vous instanciez une fois au niveau du champ d'application ou du module (par exemple, dans votre classe Application ou un module d'injection de dépendances) et que vous injectez dans les gestionnaires ou les dépôts qui lisent et écrivent l'état, plutôt que d'exposer StateStore directement aux classes d'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, )
État de lecture et d'écriture
Dans votre gestionnaire ou votre dépôt, lisez, écrivez et mettez à jour les valeurs dans le StateStore injecté à l'aide de vos définitions StateStoreKey privées, et exposez les propriétés State<T> en lecture seule et les fonctions de mutation aux appelants.
État de lecture
Utilisez getState(stateKey, defaultValue) pour récupérer un objet State<T>. Si la clé n'est pas encore initialisée, StateStore la remplit avec le paramètre defaultValue, qui est défini par défaut sur stateKey.defaultValue (vous pouvez également transmettre un defaultValue personnalisé lors de l'initialisation d'une clé non renseignée). Comme getState renvoie un State<T>, les ViewModels, les composables d'UI, les widgets et les blocs transform peuvent lire la propriété du gestionnaire de manière réactive :
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}") } }
État d'écriture
Utilisez setState(stateKey, value) dans votre gestionnaire pour initialiser ou remplacer la valeur associée à une clé :
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) } }
Modifier l'état
Si vous devez mettre à jour un état en fonction de sa valeur actuelle, utilisez updateState.
Cette opération récupère la valeur actuelle (ou utilise la valeur par défaut si elle n'est pas définie) et applique votre lambda de mise à jour :
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 } } } }
Intégrations
StateStore s'intègre à Jetpack DataStore pour conserver et restaurer l'état en cas d'arrêt du processus et de redémarrage de l'application.
DataStore
L'artefact androidx.appstate.statestore:statestore-datastore vous permet de configurer la persistance une seule fois sur votre instance StateStore partagée, de sorte que les clés persistantes de n'importe quel gestionnaire soient automatiquement enregistrées et restaurées à partir du disque à l'aide de DataStore.
Le package androidx.appstate.datastore fournit les API suivantes :
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>,
)
- Implémenter
PersistToDataStore: marquez toutStateStoreKeyqui doit être conservé en implémentant l'interfacePersistToDataStore:
@Serializable private data object FocusSessionKey : StateStoreKey<FocusSession>(defaultValue = FocusSession.Idle), PersistToDataStore
- Lier
StateStoreà DataStore : appelezsyncToDataStoreune fois lors de la configuration de votre instanceStateStorepartagée (par exemple, dans votre classeApplicationou votre module d'injection de dépendances). Étant donné quesyncToDataStoreest une fonctionsuspendqui se suspend indéfiniment pour restaurer les valeurs enregistrées à partir du disque et continuer à enregistrer les modifications futures dans lecoroutineScopefourni, appelez-la danslaunchet transmettez votreCoroutineScopeau niveau de l'application. Au démarrage,getStaterenvoie initialementdefaultValueen mémoire et met à jourStateune fois que DataStore a fini de lire le disque :
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) }
Consignes de persistance DataStore
Étant donné que syncToDataStore sérialise et écrit sur le disque chaque fois que la valeur d'une clé PersistToDataStore change, suivez ces consignes pour choisir ce que vous souhaitez conserver et quand :
- Enregistrez les codes temporels de début ou de fin au lieu de chaque graduation du minuteur : pour les minuteurs ou la progression de la lecture, conservez une valeur de référence stable (comme
endTimeMillisoustartedAtMillis) plutôt que de mettre à jour une cléPersistToDataStorechaque seconde ou chaque frame, et calculez l'affichage en direct du temps écoulé ou restant dans l'UI. - Évitez les entrées utilisateur rapides avant de mettre à jour une clé persistante : pour les brouillons de texte, les requêtes de recherche ou les curseurs, conservez l'entrée immédiate dans un
MutableStateen mémoire (ou unStateStoreKeyqui n'implémente pasPersistToDataStore) et n'appelezsetStatesur leStateStoreKeypersistant qu'une fois l'entrée stabilisée. - Limitez la taille des clés persistantes : toutes les clés
PersistToDataStored'unStateStoresont sérialisées ensemble dans un seul fichier à chaque mise à jour. Conservez l'état de session compact (par exemple, les ID actifs, les sélections de filtres ou les brouillons de formulaires en cours) et conservez les collections volumineuses ou les ensembles de données interrogeables dans Room.