StateStore Parte do Android Jetpack.
StateStore é um contêiner com segurança de tipos que armazena e expõe o estado compartilhado do aplicativo
como State do Compose em todo o app.
Casos de uso
Ao criar um StateStore com escopo de aplicativo, é possível compartilhar o estado entre
componentes de aplicativo no mesmo processo:
- Várias atividades: se o app mostrar várias atividades
simultaneamente (como uma atividade de caixa de entrada e uma de editor em um cliente
de e-mail) ou tiver uma atividade que ofereça suporte a várias instâncias
(como uma atividade de visualizador de documentos), use
StateStorepara compartilhar informações entre elas. - Atividade e serviço: sincronize o estado entre uma atividade e um
CarAppServiceem segundo plano ou uma AppFunction. - Atividade e provedor de conteúdo ou widget: compartilhe o estado entre uma atividade e um widget da tela inicial.
Se um singleton só precisar de State<T> na memória sem pesquisa com chave ou
restauração de encerramento do processo com suporte do DataStore, exponha as propriedades
mutableStateOf diretamente no singleton. Para uma comparação das APIs de estado
no seu app, consulte Escolher a API de estado certa para sua
arquitetura.
Visão geral da API
O pacote androidx.appstate.statestore fornece as classes 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,
)
Defina seu estado
Para garantir a segurança de tipos ao armazenar e recuperar valores, o StateStore usa a
classe StateStoreKey para definir as chaves usadas para ler e gravar valores. Mantenha
as definições de StateStoreKey private ou internal no gerenciador, repositório
ou módulo que possui essa fração 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)
Criar e injetar instâncias de StateStore
StateStore é uma classe comum que você instancia uma vez no escopo do aplicativo ou
do módulo (por exemplo, na classe Application ou em um módulo de injeção
de dependência) e injeta nos gerenciadores ou repositórios que leem e gravam o estado,
em vez de expor StateStore diretamente às classes da interface:
// 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, )
Ler e gravar estado
No gerenciador ou repositório, leia, grave e atualize valores no
StateStore injetado usando as definições particulares de StateStoreKey e exponha
propriedades State<T> somente leitura e funções de mutação para os autores da chamada.
Estado de leitura
Use getState(stateKey, defaultValue) para recuperar um objeto
State<T>. Se a chave ainda não tiver sido inicializada, StateStore vai preenchê-la com o
parâmetro defaultValue, que tem como padrão stateKey.defaultValue. Você também pode
transmitir um defaultValue personalizado ao inicializar uma chave não preenchida. Como
getState retorna um State<T>, ViewModels, elementos combináveis de IU, widgets e
blocos transform podem ler a propriedade do gerenciador
de forma reativa:
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}") } }
Estado de gravação
Use setState(stateKey, value) dentro do gerenciador para inicializar ou substituir o
valor associado a uma chave:
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) } }
Estado de atualização
Se você precisar atualizar um estado com base no valor atual, use updateState.
Isso recupera o valor atual (ou usa o padrão, se não estiver definido) e aplica
sua lambda de atualização:
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 } } } }
Integrações
O StateStore se integra ao DataStore do Jetpack para manter e
restaurar o estado em encerramentos de processos e reinicializações de apps.
DataStore
O artefato androidx.appstate.statestore:statestore-datastore permite
configurar a persistência uma vez na instância compartilhada de StateStore para que as
chaves persistentes de qualquer gerenciador sejam salvas e restauradas automaticamente do disco usando o
DataStore.
O pacote androidx.appstate.datastore oferece as seguintes 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>,
)
- Implementar
PersistToDataStore: marque qualquerStateStoreKeyque precise ser persistido implementando a interfacePersistToDataStore:
@Serializable private data object FocusSessionKey : StateStoreKey<FocusSession>(defaultValue = FocusSession.Idle), PersistToDataStore
- Vincular
StateStoreao DataStore: chamesyncToDataStoreuma vez ao configurar sua instância compartilhada deStateStore(por exemplo, na classeApplicationou no módulo de injeção de dependência). ComosyncToDataStoreé uma funçãosuspendque é suspensa indefinidamente para restaurar valores salvos do disco e continuar salvando mudanças futuras nocoroutineScopefornecido, chame-a dentro delaunche transmita oCoroutineScopeno nível do aplicativo. Na inicialização, ogetStateretorna inicialmentedefaultValuena memória e atualiza oStatequando o DataStore termina de ler do 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) }
Diretrizes de persistência do DataStore
Como o syncToDataStore serializa e grava no disco sempre que o valor de uma chave PersistToDataStore muda, siga estas diretrizes ao escolher o que e quando persistir:
- Salve os carimbos de data/hora de início ou término em vez de cada tique do timer: para timers ou
progresso de reprodução, mantenha um valor de referência estável (como
endTimeMillisoustartedAtMillis) em vez de atualizar uma chavePersistToDataStorea cada segundo ou frame e calcule o tempo decorrido ou restante na interface. - Evite entradas rápidas do usuário antes de atualizar uma chave persistente: para rascunhos
de texto, consultas de pesquisa ou controles deslizantes, mantenha a entrada imediata em um
MutableStatena memória (ou umStateStoreKeyque não implementePersistToDataStore) e só chamesetStatenoStateStoreKeypersistente depois que a entrada for concluída. - Mantenha as chaves persistentes leves: todas as chaves
PersistToDataStoreem umStateStoresão serializadas juntas em um único arquivo em cada atualização. Mantenha o estado compacto da sessão, como IDs ativos, seleções de filtro ou rascunhos de formulários em andamento, e mantenha grandes coleções ou conjuntos de dados consultáveis no Room.