Google is committed to advancing racial equity for Black communities. See how.

androidx.compose.runtime.snapshots

Interfaces

StateObject

Interface implemented by all snapshot aware state objects.

Classes

MutableSnapshot

A snapshot of the values return by mutable states and other state objects.

Snapshot

A snapshot of the values return by mutable states and other state objects.

SnapshotApplyResult

The result of a applying a mutable snapshot.

SnapshotStateList

An implementation of MutableList that can be observed and snapshot.

SnapshotStateMap

An implementation of MutableMap that can be observed and snapshot.

SnapshotStateObserver

StateRecord

Snapshot local value of a state object.

Exceptions

SnapshotApplyConflictException

An exception that is thrown when SnapshotApplyResult.check is called on a result of a MutableSnapshot.apply that fails to apply.

Type-aliases

SnapshotApplyObserver

Callback type for observing when a non-nested mutable snapshot is applied by calling MutableSnapshot.apply, modifying the visible global state of state objects.

SnapshotReadObserver

Callback type for observing reads of state objects in a snapshot.

SnapshotWriteObserver

Callback type for observing writes to state objects in a snapshot.

Top-level functions summary

T

Return the current readable state record for the current snapshot.

T
T.readable(state: StateObject, snapshot: Snapshot)

Return the current readable state record for the snapshot.

Flow<T>
snapshotFlow(block: () -> T)

Create a Flow from observable Snapshot state.

MutableSnapshot
takeMutableSnapshot(readObserver: SnapshotReadObserver? = null, writeObserver: SnapshotWriteObserver? = null)

Take a snapshot of the current value of all state objects that also allows the state to be changed and later atomically applied when MutableSnapshot.apply is called.

Snapshot
takeSnapshot(readObserver: SnapshotReadObserver? = null)

Take a snapshot of the current value of all state objects.

R
T.withCurrent(block: (r: T) -> R)

Provides a block with the current record, without notifying any read observers.

R
withMutableSnapshot(block: () -> R)

Take a MutableSnapshot and run block within it.

R
T.writable(state: StateObject, snapshot: Snapshot, block: T.() -> R)

Call block with a writable state record for snapshot of the given record.

R
T.writable(state: StateObject, block: T.() -> R)

Call block with a writable state record for the given record.

Top-level functions

readable

fun <T : StateRecord> T.readable(state: StateObject): T

Return the current readable state record for the current snapshot. It is assumed that this is the first record of state

readable

fun <T : StateRecord> T.readable(
    state: StateObject,
    snapshot: Snapshot
): T

Return the current readable state record for the snapshot. It is assumed that this is the first record of state

snapshotFlow

fun <T> snapshotFlow(block: () -> T): Flow<T>

Create a Flow from observable Snapshot state. (e.g. state holders returned by mutableStateOf.)

snapshotFlow creates a Flow that runs block when collected and emits the result, recording any snapshot state that was accessed. While collection continues, if a new Snapshot is applied that changes state accessed by block, the flow will run block again, re-recording the snapshot state that was accessed. If the result of block is not equal to the previous result, the flow will emit that new result. (This behavior is similar to that of Flow.distinctUntilChanged.) Collection will continue indefinitely unless it is explicitly cancelled or limited by the use of other Flow operators.

import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.snapshots.snapshotFlow
import androidx.compose.runtime.snapshots.withMutableSnapshot

// Define Snapshot state objects
var greeting by mutableStateOf("Hello")
var person by mutableStateOf("Adam")

// ...

// Create a flow that will emit whenever our person-specific greeting changes
val greetPersonFlow = snapshotFlow { "$greeting, $person" }

// ...

val collectionScope: CoroutineScope = TODO("Use your scope here")

// Collect the flow and offer greetings!
collectionScope.launch {
    greetPersonFlow.collect {
        println(greeting)
    }
}

// ...

// Change snapshot state; greetPersonFlow will emit a new greeting
withMutableSnapshot {
    greeting = "Ahoy"
    person = "Sean"
}

block is run in a read-only Snapshot and may not modify snapshot data. If block attempts to modify snapshot data, flow collection will fail with IllegalStateException.

block may run more than once for equal sets of inputs or only once after many rapid snapshot changes; it should be idempotent and free of side effects.

When working with Snapshot state it is useful to keep the distinction between events and state in mind. snapshotFlow models snapshot changes as events, but events cannot be effectively modeled as observable state. Observable state is a lossy compression of the events that produced that state.

An observable event happens at a point in time and is discarded. All registered observers at the time the event occurred are notified. All individual events in a stream are assumed to be relevant and may build on one another; repeated equal events have meaning and therefore a registered observer must observe all events without skipping.

Observable state raises change events when the state changes from one value to a new, unequal value. State change events are conflated; only the most recent state matters. Observers of state changes must therefore be idempotent; given the same state value the observer should produce the same result. It is valid for a state observer to both skip intermediate states as well as run multiple times for the same state and the result should be the same.

takeMutableSnapshot

fun takeMutableSnapshot(
    readObserver: SnapshotReadObserver? = null,
    writeObserver: SnapshotWriteObserver? = null
): MutableSnapshot

Take a snapshot of the current value of all state objects that also allows the state to be changed and later atomically applied when MutableSnapshot.apply is called. The values are preserved until Snapshot.dispose is called on the result. The global state will either see all the changes made as one atomic change, when MutableSnapshot.apply is called, or none of the changes if the mutable state object is disposed before being applied.

The values in a snapshot can be modified by calling Snapshot.enter and then, in its lambda, modify any state object. The new values of the state objects will only become visible to the global state when MutableSnapshot.apply is called.

An active snapshot (after it is created but before Snapshot.dispose is called) requires resources to track the values in the snapshot. Once a snapshot is no longer needed it should disposed by calling Snapshot.dispose.

Leaving a snapshot active could cause hard to diagnose memory leaks as values are maintained by state objects for these unneeded snapshots. Take care to always call Snapshot.dispose on all snapshots when they are no longer needed.

A nested snapshot can be taken by calling Snapshot.takeNestedSnapshot, for a read-only snapshot, or MutableSnapshot.takeNestedMutableSnapshot for a snapshot that can be changed. Nested mutable snapshots are applied to the this, the parent snapshot, when their MutableSnapshot.apply is called. Their applied changes will be visible to in this snapshot but will not be visible other snapshots (including other nested snapshots) or the global state until this snapshot is applied by calling MutableSnapshot.apply.

Once MutableSnapshot.apply is called on this, the parent snapshot, all calls to MutableSnapshot.apply on an active nested snapshot will fail.

Changes to a mutable snapshot are isolated, using snapshot isolation, from all other snapshots. Their changes are only visible as global state or to new snapshots once MutableSnapshot.apply is called.

Applying a snapshot can fail if currently visible changes to the state object conflicts with a change made in the snapshot.

When in a mutable snapshot, takeMutableSnapshot creates a nested snapshot of the current mutable snapshot. If the current snapshot is read-only, an exception is thrown. The current snapshot is the result of calling currentSnapshot which is updated by calling Snapshot.enter which makes the Snapshot the current snapshot while in its lambda.

Composition uses mutable snapshots to allow changes made in a Composable functions to be temporarily isolated from the global state and is later applied to the global state when the composition is applied. If MutableSnapshot.apply fails applying this snapshot, the snapshot and the changes calculated during composition are disposed and a new composition is scheduled to be calculated again.

Parameters
readObserver: SnapshotReadObserver? = null

called when any state object is read in the lambda passed to Snapshot.enter or in the Snapshot.enter of any nested snapshots.

Composition, layout and draw use readObserver to implicitly subscribe to changes to state objects to know when to update.

writeObserver: SnapshotWriteObserver? = null

called when a state object is created or just before it is written to the first time in the snapshot or a nested mutable snapshot. This might be called several times for the same object if nested mutable snapshots are created.

Composition uses writeObserver to track when a state object is modified during composition in order to invalidate the reads that have not yet occurred. This allows a single pass of composition for state objects that are written to before they are read (such as modifying the value of a dynamic ambient provider).

takeSnapshot

fun takeSnapshot(readObserver: SnapshotReadObserver? = null): Snapshot

Take a snapshot of the current value of all state objects. The values are preserved until Snapshot.dispose is called on the result.

The readObserver parameter can be used to track when all state objects are read when in Snapshot.enter. A SnapshotApplyObserver can be registered using Snapshot.registerApplyObserver to observe modification of state objects.

An active snapshot (after it is created but before Snapshot.dispose is called) requires resources to track the values in the snapshot. Once a snapshot is no longer needed it should disposed by calling Snapshot.dispose.

Leaving a snapshot active could cause hard to diagnose memory leaks values as are maintained by state objects for these unneeded snapshots. Take care to always call Snapshot.dispose on all snapshots when they are no longer needed.

Composition uses both of these to implicitly subscribe to changes to state object and automatically update the composition when state objects read during composition change.

A nested snapshot can be taken of a snapshot which is an independent read-only copy of the snapshot and can be disposed independently. This is used by takeSnapshot when in a read-only snapshot for API consistency allowing the result of takeSnapshot to be disposed leaving the parent snapshot active.

Parameters
readObserver: SnapshotReadObserver? = null called when any state object is read in the lambda passed to Snapshot.enter or in the Snapshot.enter of any nested snapshot.

withCurrent

inline fun <T : StateRecord, R> T.withCurrent(block: (r: T) -> R): R

Provides a block with the current record, without notifying any read observers.

See Also

withMutableSnapshot

inline fun <R> withMutableSnapshot(block: () -> R): R

Take a MutableSnapshot and run block within it. When block returns successfully, attempt to MutableSnapshot.apply the snapshot. Returns the result of block or throws SnapshotApplyConflictException if snapshot changes attempted by block could not be applied.

Prior to returning, any changes made to snapshot state (e.g. state holders returned by androidx.compose.runtime.mutableStateOf are not visible to other threads. When withMutableSnapshot returns successfully those changes will be made visible to other threads and any snapshot observers (e.g. snapshotFlow) will be notified of changes.

block must not suspend if withMutableSnapshot is called from a suspend function.

writable

inline fun <T : StateRecord, R> T.writable(
    state: StateObject,
    snapshot: Snapshot,
    block: T.() -> R
): R

Call block with a writable state record for snapshot of the given record. It is assumed that this is called for the first state record in a state object. If the snapshot is read-only calling this will throw.

writable

inline fun <T : StateRecord, R> T.writable(
    state: StateObject,
    block: T.() -> R
): R

Call block with a writable state record for the given record. It is assumed that this is called for the first state record in a state object. A record is writable if it was created in the current mutable snapshot.