StateStore Parte de Android Jetpack.
StateStore es un contenedor con seguridad de tipos que almacena y expone el estado compartido de la aplicación como State de Compose en toda la app.
Casos de uso
Si creas un StateStore con alcance de aplicación, puedes compartir el estado entre los componentes de la aplicación dentro del mismo proceso:
- Varias actividades: Si tu app muestra varias actividades de forma simultánea (como una actividad de Recibidos y una actividad de editor en un cliente de correo electrónico) o tiene una actividad que admite instancias múltiples (como una actividad de visor de documentos), puedes usar
StateStorepara compartir información entre ellas. - Actividad y servicio: Sincroniza el estado entre una actividad y un
CarAppServiceo una AppFunction en segundo plano. - Actividad y proveedor de contenido o widget: Comparte el estado entre una actividad y un widget de la pantalla principal.
Si un singleton solo necesita State<T> en la memoria sin búsqueda con clave ni restablecimiento de cierre del proceso respaldado por DataStore, puedes exponer las propiedades de mutableStateOf directamente en el singleton. Para obtener una comparación de las APIs de estado en tu app, consulta Elige la API de estado adecuada para tu arquitectura.
Descripción general de la API
El paquete androidx.appstate.statestore proporciona las clases StateStore y
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,
)
Define tu estado
Para garantizar la seguridad de tipos cuando se almacenan y recuperan valores, StateStore usa la clase StateStoreKey para definir las claves que se usan para leer y escribir valores. Mantén las definiciones de StateStoreKey private o internal en el administrador, el repositorio o el módulo que posee esa porción de estado:
@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 inserta instancias de StateStore
StateStore es una clase normal de la que creas una instancia una vez en el alcance de la aplicación o del módulo (por ejemplo, en tu clase Application o en un módulo de inserción de dependencias) y que insertas en los administradores o repositorios que leen y escriben el estado, en lugar de exponer StateStore directamente a las clases de la IU:
// 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, )
Estado de lectura y escritura
Dentro de tu administrador o repositorio, lee, escribe y actualiza valores en el
StateStore insertado con tus definiciones privadas de StateStoreKey, y expón
propiedades de State<T> de solo lectura y funciones de mutación a los emisores.
Estado de lectura
Usa getState(stateKey, defaultValue) para recuperar un objeto State<T>. Si la clave aún no se inicializó, StateStore la propaga con el parámetro defaultValue, que se establece de forma predeterminada en stateKey.defaultValue (o puedes pasar un defaultValue personalizado cuando inicialices una clave sin propagar). Debido a que getState devuelve un State<T>, los ViewModels, los elementos componibles de la IU, los widgets y los bloques transform pueden leer la propiedad del administrador de forma reactiva:
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}") } }
Escribir estado
Usa setState(stateKey, value) dentro de tu administrador para inicializar o reemplazar el valor asociado con una clave:
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) } }
Actualizar estado
Si necesitas actualizar un estado en función de su valor actual, usa updateState.
Esto recupera el valor actual (o usa el predeterminado si no se estableció) y aplica tu lambda de actualización:
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 } } } }
Integraciones
StateStore se integra en DataStore de Jetpack para conservar y restablecer el estado en los cierres de procesos y los reinicios de apps.
DataStore
El artefacto androidx.appstate.statestore:statestore-datastore te permite configurar la persistencia una vez en tu instancia compartida de StateStore para que las claves persistentes de cualquier administrador se guarden y restablezcan automáticamente desde el disco con DataStore.
El paquete androidx.appstate.datastore proporciona las siguientes 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>,
)
- Implementa
PersistToDataStore: Marca cualquierStateStoreKeyque deba conservarse implementando la interfazPersistToDataStore:
@Serializable private data object FocusSessionKey : StateStoreKey<FocusSession>(defaultValue = FocusSession.Idle), PersistToDataStore
- Vincula
StateStorea DataStore: Llama asyncToDataStoreuna vez cuando configures tu instancia compartida deStateStore(por ejemplo, en tu claseApplicationo módulo de inserción de dependencias). ComosyncToDataStorees una funciónsuspendque se suspende de forma indefinida para restablecer los valores guardados del disco y seguir guardando los cambios futuros en elcoroutineScopeproporcionado, llámala dentro delaunchy pasa tuCoroutineScopea nivel de la aplicación. En el inicio,getStatemuestra inicialmentedefaultValueen la memoria y actualizaStateuna vez que DataStore termina de leer desde el 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) }
Lineamientos de persistencia de DataStore
Debido a que syncToDataStore serializa y escribe en el disco cada vez que cambia el valor de una clave PersistToDataStore, sigue estos lineamientos cuando elijas qué conservar y cuándo hacerlo:
- Guarda las marcas de tiempo de inicio o finalización en lugar de cada tic del temporizador: Para los temporizadores o el progreso de la reproducción, conserva un valor de referencia estable (como
endTimeMillisostartedAtMillis) en lugar de actualizar una clavePersistToDataStorecada segundo o fotograma, y calcula la visualización transcurrida o restante en vivo en la IU. - Estabiliza la entrada rápida del usuario antes de actualizar una clave persistente: Para los borradores de texto, las búsquedas o los controles deslizantes, mantén la entrada inmediata en un
MutableStateen la memoria (o unStateStoreKeyque no implementePersistToDataStore) y solo llama asetStateen elStateStoreKeypersistente después de que se establezca la entrada. - Mantén las claves persistentes ligeras: Todas las claves
PersistToDataStorede unStateStorese serializan juntas en un solo archivo en cada actualización. Conserva el estado de sesión compacto, como los IDs activos, las selecciones de filtros o los borradores de formularios en curso, y mantén las colecciones grandes o los conjuntos de datos consultables en Room.