Lesson 1
A state object is a record chain
Explain how one snapshot-aware state object stores versioned records and selects a readable value.
Status: A state object owning versioned records is Durable . The linked-list fields, snapshot-ID algorithm, and record-reuse helpers are Version-specific at the pinned revision.
Outcome
Given a MutableState, you should be able to explain why the same object can return different values in different snapshots without copying every object it references.
The object owns versions, not a copied object graph
StateObject is the snapshot module’s interface for an object with state records. It exposes the first record in a linked list and a method for prepending a record. A record is a StateRecord with a snapshot ID, a next link, and copy/assignment operations (Snapshot.kt).
A mutableStateOf("ready") therefore does not mean “make a snapshot of the whole model.” The state object keeps records for the values that snapshots may need. The snapshot chooses which record is visible. The record chain is local to that state object; it is not a second copy of an application tree.
flowchart LR
State[StateObject] --> Newest[Newest record]
Newest --> Older[Older record]
Older --> Base[Base record]
Snapshot[Current snapshot] --> Select[Select valid record]
Newest --> Select
Older --> Select
Base --> Select
Select --> Value[Visible value]At this revision, SnapshotMutableStateImpl stores a StateStateRecord chain. Its value getter calls readable, and its setter uses overwritable after the policy says the value is not equivalent (SnapshotState.kt). The concrete class is private to the runtime, so treat its name as Version-specific
, not as an application extension point.
Readability is a snapshot question
The pinned readable helper walks the chain and keeps the valid record with the greatest snapshot ID. A candidate is valid when its ID is not reserved, is no later than the current snapshot, and is not in that snapshot’s invalid-ID set (Snapshot.kt). The winner is therefore not simply “the first record” or “the newest value globally.” It is the newest record visible to this snapshot.
readable(state) also invokes the current snapshot’s read observer before selecting a record. That callback is one of the ways an observed read can become an edge in a larger system; Lesson 5, “Reads become observation edges,” follows the composition and observer maps. The read itself still answers only a consistency question: which record is visible here?
A write gets a record for its snapshot
The writable helper first finds the record readable by the current snapshot. If that record was born in the current snapshot, it can be changed. Otherwise, the runtime copies the readable data into a reusable or new record, gives that record the current snapshot ID, and prepends it to the state object. After the block, notifyWrite advances the snapshot write count and invokes its write observer (Snapshot.kt).
This is why a write inside an isolated mutable snapshot can be immediately visible to that snapshot while remaining invisible to the global snapshot. The value lives in a record that the global snapshot does not select yet. The record chain is the mechanism; no global lock is the mental model.
Reproduce the bounded behavior
This is a pinned upstream-test procedure, not an experiment run for this lesson. In a checkout of AndroidX at cc1caf65677fc10a8ce8116eba46e716f4cef222, run:
./gradlew :compose:runtime:runtime:desktopTest \
--tests 'androidx.compose.runtime.snapshots.SnapshotTests.testRecordsAreReusedCorrectly' \
--tests 'androidx.compose.runtime.snapshots.SnapshotTests.testSnapshotStateIsBornAccessible'
Use SnapshotTests.kt as the exact control and mutation fixture. The first test keeps a read-only snapshot at value 1 while later global writes advance the state; the second checks that a state created in a mutable snapshot exposes its initial value before the snapshot applies, but its later modification only after apply. Control: remove the later write or read outside readable1; the retained snapshot should still see its own version. Interpretation limit: these assertions demonstrate visibility and record retention. They do not expose private chain order, prove a performance property, or cover every platform’s synchronization implementation.
Misconceptions
- “A snapshot copies the whole object graph.” Snapshot records are attached to individual state objects.
- “The first record is always the current value.” The current snapshot selects the highest valid record.
- “A write changes the global value immediately.” It first changes a record visible to the active mutable snapshot.
Check yourself
Two records have IDs 8 and 11. The current snapshot is 12, but ID 11 is invalid for it. Which record is readable, and why is “highest ID wins” incomplete?
Source notes
| Claim | Direct evidence | Status |
|---|---|---|
StateRecord is a linked record with snapshot identity | Snapshot.kt | Durable
Version-specific |
| Readability selects the greatest valid record for a snapshot | Snapshot.kt | Version-specific |
mutableStateOf reads and writes through snapshot records | SnapshotState.kt | Version-specific |
| Upstream tests bound record retention and birth visibility | SnapshotTests.kt | Version-specific |
Freshness
Refresh this lesson when StateRecord, readable, writable, snapshot-ID validity, or record reclamation changes. The pinned history includes a recent deprecation-message change for StateRecord.withCurrent (5326fc2); that is a reminder that helper names and compatibility surfaces can move even when the record-chain model remains useful.
Finished this lesson?
Your progress stays only in this browser.