StateStore Part of Android Jetpack.
StateStore is a type-safe container that stores and exposes shared application
state as Compose State across your app.
Use cases
By creating an application-scoped StateStore, you can share state between
application components within the same process:
- Multiple activities: If your app displays multiple activities
simultaneously (such as an inbox activity and an editor activity in an email
client) or has an activity that supports multi-instance
(such as a document viewer activity), you can use
StateStoreto share information between them. - Activity and service: Synchronize state between an activity and a
background
CarAppServiceor AppFunction. - Activity and content provider or widget: Share state between an activity and a home screen widget.
If a singleton only needs in-memory State<T> without keyed lookup or
DataStore-backed process-death restoration, you can expose mutableStateOf
properties directly on the singleton instead. For a comparison of state APIs
across your app, see Choose the right state API across your
architecture.
API overview
The androidx.appstate.statestore package provides the StateStore and
StateStoreKey classes:
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 your state
To ensure type safety when storing and retrieving values, StateStore uses the
StateStoreKey class to define the keys used to read and write values. Keep
StateStoreKey definitions private or internal to the manager, repository,
or module that owns that slice of state:
@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)
Create and inject StateStore instances
StateStore is a regular class that you instantiate once at the application or
module scope (for example, in your Application class or a dependency injection
module) and inject into the managers or repositories that read and write state,
rather than exposing StateStore directly to UI classes:
// 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, )
Read and write state
Inside your manager or repository, read, write, and update values in the
injected StateStore using your private StateStoreKey definitions, and expose
read-only State<T> properties and mutation functions to callers.
Read state
Use getState(stateKey, defaultValue) to retrieve a State<T>
object. If the key isn't initialized yet, StateStore populates it with the
defaultValue parameter, which defaults to stateKey.defaultValue (or you can
pass a custom defaultValue when initializing an unpopulated key). Because
getState returns a State<T>, ViewModels, UI composables, widgets, and
transform blocks can read the manager's property
reactively:
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}") } }
Write state
Use setState(stateKey, value) inside your manager to initialize or replace the
value associated with a key:
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) } }
Update state
If you need to update a state based on its current value, use updateState.
This retrieves the current value (or uses the default if not set) and applies
your update lambda:
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 } } } }
Integrations
StateStore integrates with Jetpack DataStore to persist and
restore state across process death and app restarts.
DataStore
The androidx.appstate.statestore:statestore-datastore artifact lets you
configure persistence once on your shared StateStore instance so any
manager's persisted keys automatically save and restore from disk using
DataStore.
The androidx.appstate.datastore package provides the following 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>,
)
- Implement
PersistToDataStore: Mark anyStateStoreKeythat should be persisted by implementing thePersistToDataStoreinterface:
@Serializable private data object FocusSessionKey : StateStoreKey<FocusSession>(defaultValue = FocusSession.Idle), PersistToDataStore
- Bind
StateStoreto DataStore: CallsyncToDataStoreonce when setting up your sharedStateStoreinstance (for example, in yourApplicationclass or dependency injection module). BecausesyncToDataStoreis asuspendfunction that suspends indefinitely to restore saved values from disk and continue saving future changes in the providedcoroutineScope, call it insidelaunchand pass your application-levelCoroutineScope. On startup,getStateinitially returnsdefaultValuein memory and updates theStateonce DataStore finishes reading from disk:
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) }
DataStore persistence guidelines
Because syncToDataStore serializes and writes to disk whenever a
PersistToDataStore key's value changes, follow these guidelines when choosing
what and when to persist:
- Save start or end timestamps instead of every timer tick: For timers or
playback progress, persist a stable reference value (such as
endTimeMillisorstartedAtMillis) rather than updating aPersistToDataStorekey every second or frame, and compute the live elapsed or remaining display in the UI. - Debounce rapid user input before updating a persisted key: For text
drafts, search queries, or sliders, hold the immediate input in an in-memory
MutableState(or aStateStoreKeythat doesn't implementPersistToDataStore) and only callsetStateon the persistedStateStoreKeyafter the input settles. - Keep persisted keys lightweight: All
PersistToDataStorekeys in aStateStoreare serialized together into a single file on each update. Persist compact session state—such as active IDs, filter selections, or in-progress form drafts—and keep large collections or queryable datasets in Room.