Snapshot
public sealed class Snapshot(
snapshotId: SnapshotId,
/** A set of all the snapshots that should be treated as invalid. */
internal open var invalid: SnapshotIdSet,
)
A snapshot of the values return by mutable states and other state objects. All state object will have the same value in the snapshot as they had when the snapshot was created unless they are explicitly changed in the snapshot.
To enter a snapshot call enter
. The snapshot is the current snapshot as returned by
currentSnapshot
until the control returns from the lambda (or until a nested enter
is
called). All state objects will return the values associated with this snapshot, locally in the
thread, until enter
returns. All other threads are unaffected.
Snapshots can be nested by calling takeNestedSnapshot
.
Secondary Constructors
protected constructor(id: Int, invalid: SnapshotIdSet) : this(id.toSnapshotId(), invalid)
Functions
public open fun dispose()
Dispose the snapshot. Neglecting to dispose a snapshot will result in difficult to diagnose memory leaks as it indirectly causes all state objects to maintain its value for the un-disposed snapshot.
public abstract fun takeNestedSnapshot(readObserver: ((Any) -> Unit)? = null): Snapshot
Take a snapshot of the state values in this snapshot. The resulting Snapshot
is read-only.
All nested snapshots need to be disposed by calling dispose
before resources associated
with this snapshot can be collected. Nested snapshots are still valid after the parent has
been disposed.
public abstract fun hasPendingChanges(): Boolean
Whether there are any pending changes in this snapshot. These changes are not visible until the snapshot is applied.
public inline fun <T> enter(block: () -> T): T
Enter the snapshot. In block
all state objects have the value associated with this
snapshot. The value of currentSnapshot
will be this snapshot until this block
returns or
a nested call to enter
is called. When block
returns, the previous current snapshot is
restored if there was one.
All changes to state objects inside block
are isolated to this snapshot and are not visible
to other snapshot or as global state. If this is a readOnly
snapshot, any changes to state
objects will throw an IllegalStateException
.
For a MutableSnapshot
, changes made to a snapshot inside block
can be applied atomically
to the global state (or to its parent snapshot if it is a nested snapshot) by calling
MutableSnapshot.apply
.
public fun unsafeEnter(): Snapshot?
Enter the snapshot, returning the previous Snapshot
for leaving this snapshot later using
unsafeLeave
. Prefer enter
or asContextElement
instead of using unsafeEnter
directly
to prevent mismatched unsafeEnter
/unsafeLeave
calls.
After returning all state objects have the value associated with this snapshot. The value of
currentSnapshot
will be this snapshot until unsafeLeave
is called with the returned
Snapshot
or another call to unsafeEnter
or enter
is made.
All changes to state objects until another snapshot is entered or this snapshot is left are
isolated to this snapshot and are not visible to other snapshot or as global state. If this
is a readOnly
snapshot, any changes to state objects will throw an IllegalStateException
.
For a MutableSnapshot
, changes made to a snapshot can be applied atomically to the global
state (or to its parent snapshot if it is a nested snapshot) by calling
MutableSnapshot.apply
.
public fun unsafeLeave(oldSnapshot: Snapshot?)
Leave the snapshot, restoring the oldSnapshot
before returning. See unsafeEnter
.
Companion Object
Properties
public val current: Snapshot
Return the thread's active snapshot. If no thread snapshot is active then the current global snapshot is used.
public val isInSnapshot: Boolean
Return true
if the thread is currently in the context of a snapshot.
public val isApplyObserverNotificationPending: Boolean
Returns whether any threads are currently in the process of notifying observers about changes to the global snapshot.
public const val PreexistingSnapshotId: Int
All new state objects initial state records should be PreexistingSnapshotId
which then
allows snapshots outside the creating snapshot to access the object with its initial
state.
Methods
public fun takeSnapshot(readObserver: ((Any) -> Unit)? = 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 snapshot apply observer 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 | called when any state object is read in the lambda passed to Snapshot.enter or in the Snapshot.enter of any nested snapshot. |
public fun takeMutableSnapshot(
readObserver: ((Any) -> Unit)? = null,
writeObserver: ((Any) -> Unit)? = 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.
Composition, layout and draw use readObserver
to implicitly subscribe to changes to
state objects to know when to update.
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).
Parameters
readObserver | called when any state object is read in the lambda passed to Snapshot.enter or in the Snapshot.enter of any nested snapshots. |
writeObserver | 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. |
public inline fun <T> global(block: () -> T): T
Escape the current snapshot, if there is one. All state objects will have the value
associated with the global while the block
lambda is executing.
Returns
the result of block |
public 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. androidx.compose.runtime.snapshotFlow
) will be
notified of changes.
block
must not suspend if withMutableSnapshot
is called from a suspend function.
public fun <T> observe(
readObserver: ((Any) -> Unit)? = null,
writeObserver: ((Any) -> Unit)? = null,
block: () -> T,
): T
Observe reads and or write of state objects in the current thread.
This only affects the current snapshot (if any) and any new snapshots create from
Snapshot.takeSnapshot
and takeMutableSnapshot
. It will not affect any snapshots
previous created even if Snapshot.enter
is called in block
.
Parameters
readObserver | called when any state object is read. |
writeObserver | 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. |
block | the code the readObserver and writeObserver will be observing. Once block returns, the readObserver and writeObserver will no longer be called. |
public inline fun <T> withoutReadObservation(block: @DisallowComposableCalls () -> T): T
Passed block
will be run with all the currently set snapshot read observers disabled.
public fun registerApplyObserver(observer: (Set<Any>, Snapshot) -> Unit): ObserverHandle
Register an apply listener that is called back when snapshots are applied to the global state.
Returns
ObserverHandle to unregister observer . |
public fun registerGlobalWriteObserver(observer: ((Any) -> Unit)): ObserverHandle
Register an observer of the first write to the global state of a global state object
since the last call to sendApplyNotifications
.
Composition uses this to schedule a new composition whenever a state object that was read in composition is modified.
State objects can be sent to the apply observer that have not been sent to global write
observers. This happens for state objects inside MutableSnapshot
that is later applied
by calling MutableSnapshot.apply
.
This should only be used to determine if a call to sendApplyNotifications
should be
scheduled to be called.
Returns
ObserverHandle to unregister observer . |
public fun notifyObjectsInitialized(): Unit
Notify the snapshot that all objects created in this snapshot to this point should be considered initialized. If any state object is are modified passed this point it will appear as modified in the snapshot and any applicable snapshot write observer will be called for the object and the object will be part of the a set of mutated objects sent to any applicable snapshot apply observer.
Unless notifyObjectsInitialized
is called, state objects created in a snapshot are not
considered modified by the snapshot even if they are modified after construction.
Compose uses this between phases of composition to allow observing changes to state objects create in a previous phase.
public fun sendApplyNotifications()
Send any pending apply notifications for state objects changed outside a snapshot.
Apply notifications for state objects modified outside snapshot are deferred until method
is called. This method is implicitly called whenever a non-nested MutableSnapshot
is
applied making its changes visible to all new, non-nested snapshots.
Composition schedules this to be called after changes to state objects are detected an
observer registered with registerGlobalWriteObserver
.
@InternalComposeApi public fun openSnapshotCount(): Int