Lesson 3
Remember, retain, and saved state are different lifetimes
Separate slot memory, in-memory transient retention, saved-state restoration, and process lifetime.
Status: The distinction between slot, retained-memory, saved-state, and process lifetimes is Durable . The retention-store protocol and current release mapping are Version-specific .
Outcome
Given a value and a destruction event, predict whether it is recreated, restored from memory, restored from saved state, or lost with the process—and name the owner that decides.
Four owners, four failure modes
remember is composition memory. Its calculation is cached in a slot and reused while the remembered identity remains in the composition; when that group is forgotten, the slot value is no longer an owner of the value. The public implementation is a thin call to composer caching and key comparison (Composables.kt). A recomposition is not automatically a destruction event: the same remembered group can simply be revisited.
retain also starts with a remembered holder, but it asks LocalRetainedValuesStore whether a value that exited composition can be consumed. The pinned implementation creates a RetainKeys identity from positional key, value type hash, and optional keys, then calls consumeExitedValueOrDefault; only a miss runs calculation() (Retain.kt, Retain.kt). The store may hold the object after its composable has left, then return it if matching content is recreated.
This is memory retention, not serialization. The retain release entry says retained values have a shorter lifespan than saved values and are not serialized (Compose Runtime 1.10.0-alpha01 notes). rememberSaveable delegates to saved-state infrastructure; its documented purpose is to survive activity or process recreation when the saved-state owner can restore the saved representation (state-saving guidance). A killed process cannot hand a heap object in a RetainedValuesStore back to a new process. Saved state also is not a promise to serialize arbitrary object graphs: the provider and value must satisfy its saved-state constraints.
| Boundary | remember | retain | rememberSaveable |
|---|---|---|---|
| Ordinary recomposition | Reuses a matching slot | Reuses its remembered holder | Reuses its remembered holder and saved-state registration |
| Transient composition exit and re-entry | Recreates after the slot is forgotten | May restore the same heap object when its store and keys match | Depends on the saved-state owner and registration; it is not a heap-retention store |
| Activity configuration recreation | Usually recreates with the composition | May bridge the recreation when the host preserves the retained store | Restores a saved representation when the owner can restore it |
| Process death | Lost | Lost | May restore saved data; it does not restore the old heap object |
flowchart LR
Remember[remember slot] -->|Group forgotten| Gone[Value released]
Retain[retain store] -->|Transient exit| Heap[Value kept in memory]
Save[rememberSaveable] -->|Saved registry| Bundle[Saved representation]
Process[Process death] -->|No heap owner| Recreate[Recreate or restore saved data]
Heap -->|Store retired| Gone
Bundle --> RecreateThe owner matters more than the API spelling. A ManagedRetainedValuesStore can be installed under LocalRetainedValuesStoreProvider; its default state is enabled, and it retains exited values until its content re-enters or the store is disabled/disposed (ManagedRetainedValuesStore.kt). The default ForgetfulRetainedValuesStore makes retain behave like remember, while still dispatching RetainObserver callbacks (ForgetfulRetainedValuesStore.kt). A store is therefore a policy and ownership boundary, not a hidden saved-state registry.
Released status without overclaiming
At the pinned source, retain is declared @Composable with no @ExperimentalComposeApi or @ExperimentalComposeRuntimeApi annotation. The runtime-retain API signature is in api/current.txt, and the release notes introduce it in 1.10.0-alpha01; Maven metadata accessed 2026-07-12 lists stable runtime-retain:1.10.0, 1.10.6, and later lines. The safe claim is: the API is released and callable in those artifact lines; private holder/store behavior must be checked against the artifact actually shipped. The release notes also document a breaking store-management redesign in 1.10.0-beta02; custom stores written against earlier alpha APIs are not interchangeable with the current interface (alpha01 notes, beta02 notes).
Reproduce the lifetime matrix
At the pinned AndroidX checkout, run:
./gradlew :compose:runtime:runtime-retain:desktopTest \
--tests 'androidx.compose.runtime.retain.RetainTests.retain_notRetaining_reconstruct' \
--tests 'androidx.compose.runtime.retain.RetainTests.retain_retaining_reconstruct' \
--tests 'androidx.compose.runtime.retain.RetainTests.retain_recomputesForNewKeys_whenNotRetaining'
Controls: disable retention before removing content; then enable retention and remove the same content; finally change a retain key. Expected: the first path creates a new value on re-entry, the retaining path reuses the old object, and a different key retires the old value rather than restoring it. Interpretation limit: this tests in-memory store policy, not saved-state serialization, Android activity recreation, or process death. This procedure was not executed for this lesson.
Misconceptions
- “
retainis a renamedrememberSaveable.” It keeps a heap object in a store; it does not serialize it. - “Retained values survive process death.” They disappear with the process; only an independent saved-state mechanism can restore data.
- “
rememberalways recalculates during recomposition.” Recomposition revisits code; a matching remembered identity can reuse the slot. - “A
ManagedRetainedValuesStoreis automatically immortal.”disposeretires held values; failing to dispose an orphaned store can leak them.
Check yourself
A screen leaves composition during navigation, returns with the same retain keys, and then the app process is killed before the next launch. Which boundary can restore the heap object, and which boundary can only restore a saved representation?
Evidence ledger
| Claim | Direct evidence | Status |
|---|---|---|
remember caches through composer slots and key comparison | Composables.kt | Durable
Version-specific |
retain resolves an exited value through LocalRetainedValuesStore before calculating | Retain.kt and Retain.kt | Version-specific |
| Retain is in-memory, shorter-lived, and not serialized like saved state | 1.10.0-alpha01 release notes and saved-state guidance | Durable
Version-specific |
| The default store and managed store determine whether exited values remain available | LocalRetainedValuesStore.kt and ManagedRetainedValuesStore.kt | Version-specific |
Freshness
Refresh when RetainKeys, the default store, managed-store disposal, saved-state guarantees, or the release mapping changes. Do not use this lesson to infer process-restoration behavior from a heap-only test.
Finished this lesson?
Your progress stays only in this browser.