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 StateStore to share information between them.
  • Activity and service: Synchronize state between an activity and a background CarAppService or 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>,
)
  1. Implement PersistToDataStore: Mark any StateStoreKey that should be persisted by implementing the PersistToDataStore interface:

@Serializable
private data object FocusSessionKey :
    StateStoreKey<FocusSession>(defaultValue = FocusSession.Idle),
    PersistToDataStore

  1. Bind StateStore to DataStore: Call syncToDataStore once when setting up your shared StateStore instance (for example, in your Application class or dependency injection module). Because syncToDataStore is a suspend function that suspends indefinitely to restore saved values from disk and continue saving future changes in the provided coroutineScope, call it inside launch and pass your application-level CoroutineScope. On startup, getState initially returns defaultValue in memory and updates the State once 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 endTimeMillis or startedAtMillis) rather than updating a PersistToDataStore key 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 a StateStoreKey that doesn't implement PersistToDataStore) and only call setState on the persisted StateStoreKey after the input settles.
  • Keep persisted keys lightweight: All PersistToDataStore keys in a StateStore are 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.