retain

Composable Function

Common
@Composable
public inline fun <reified T> retain(noinline calculation: () -> T): T

Remember the value produced by calculation and retain it in the current RetainScope. A retained value is one that is persisted in memory to survive transient destruction and recreation of a portion or the entirety of the content in the composition hierarchy. Some examples of when content is transient destroyed occur include:

  • Navigation destinations that are on the back stack, not currently visible, and not composed
  • UI components that are collapsed, not rendering, and not composed
  • On Android, composition hierarchies hosted by an Activity that is being destroyed and recreated due to a configuration change

When a value retained by retain leaves the composition hierarchy during one of these retention scenarios, the LocalRetainScope will persist it until the content is recreated. If an instance of this function then re-enters the composition hierarchy during the recreation, it will return the retained value instead of invoking calculation again.

If this function leaves the composition hierarchy when the LocalRetainScope is not keeping values that exit the composition, the value will be discarded immediately.

The lifecycle of the retained value can be observed by implementing RetainObserver. Callbacks from RememberObserver are never invoked on objects retained this way. It is invalid to retain an object that is a RememberObserver but not a RetainObserver, and an exception will be thrown.

The lifecycle of a retained value is shown in the diagram below. This diagram tracks how a retained value is held through its lifecycle and when it transitions between states.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      β”‚
β”‚ retain(keys) { ... } β”‚
β”‚        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”‚
└─────────  value: T  β”œβ”˜        β””β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜           β”‚   β–²       Exitβ”‚   β”‚Enter
 compositionβ”‚   β”‚composition  or changeβ”‚   β”‚       keysβ”‚   β”‚                         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”           β”‚   β”œβ”€β”€β”€No retained value──────   calculation: () -> T   β”‚           β”‚   β”‚   or different keys     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜           β”‚   β”‚                         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”           β”‚   └───Re-enter composition───    Local RetainScope     β”‚           β”‚       with the same keys    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜           β”‚                                           β–²   β”‚           β”‚                      β”Œβ”€Yesβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚ value not           β”‚                      β”‚                        β”‚ restored and           β”‚   .──────────────────┴──────────────────.     β”‚ scope stops           └─▢(   RetainScope.isKeepingExitedValues   )    β”‚ keeping exited               `──────────────────┬──────────────────'     β”‚ values                                  β”‚                        β–Ό                                  β”‚      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                  └─No──▢│     value is retired     β”‚                                         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Important: Retained values are held longer than the lifespan of the composable they are associated with. This can cause memory leaks if a retained object is kept beyond its expected lifetime. Be cautious with the types of data that you retain. Never retain an Android Context or an object that references a Context (including View), either directly or indirectly. To mark that a custom class should not be retained (possibly because it will cause a memory leak), you can annotate your class definition with androidx.compose.runtime.annotation.DoNotRetain.

Parameters

calculationA computation to invoke to create a new value, which will be used when a previous one is not available to return because it was neither remembered nor retained.

Returns

The result of calculation
Common
@Composable
public inline fun <reified T> retain(vararg keys: Any?, noinline calculation: () -> T): T

Remember the value produced by calculation and retain it in the current RetainScope. A retained value is one that is persisted in memory to survive transient destruction and recreation of a portion of the entirety of the composition hierarchy. Some examples of when this transient destruction occur include:

  • Navigation destinations that are on the back stack, not currently visible, and not composed
  • UI components that are collapsed, not rendering, and not composed
  • On Android, composition hierarchies hosted by an Activity that is being destroyed and recreated due to a configuration change

When a value retained by retain leaves the composition hierarchy during one of these retention scenarios, the LocalRetainScope will persist it until the content is recreated. If an instance of this function then re-enters the composition hierarchy during the recreation, it will return the retained value instead of invoking calculation again.

If this function leaves the composition hierarchy when the LocalRetainScope is not keeping values that exit the composition or is invoked with list of keys that are not all equal (==) to the values they had in the previous composition, the value will be discarded immediately and calculation will execute again when a new value is needed.

The lifecycle of the retained value can be observed by implementing RetainObserver. Callbacks from RememberObserver are never invoked on objects retained this way. It is illegal to retain an object that is a RememberObserver but not a RetainObserver.

Keys passed to this composable will be kept in-memory while the computed value is retained for comparison against the old keys until the value is retired. Keys are allowed to implement RememberObserver arbitrarily, unlike the values returned by calculation. If a key implements RetainObserver, it will not receive retention callbacks from this usage.

The lifecycle of a retained value is shown in the diagram below. This diagram tracks how a retained value is held through its lifecycle and when it transitions between states.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      β”‚
β”‚ retain(keys) { ... } β”‚
β”‚        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”‚
└─────────  value: T  β”œβ”˜        β””β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜           β”‚   β–²       Exitβ”‚   β”‚Enter
 compositionβ”‚   β”‚composition  or changeβ”‚   β”‚       keysβ”‚   β”‚                         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”           β”‚   β”œβ”€β”€β”€No retained value──────   calculation: () -> T   β”‚           β”‚   β”‚   or different keys     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜           β”‚   β”‚                         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”           β”‚   └───Re-enter composition───    Local RetainScope     β”‚           β”‚       with the same keys    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜           β”‚                                           β–²   β”‚           β”‚                      β”Œβ”€Yesβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚ value not           β”‚                      β”‚                        β”‚ restored and           β”‚   .──────────────────┴──────────────────.     β”‚ scope stops           └─▢(   RetainScope.isKeepingExitedValues   )    β”‚ keeping exited               `──────────────────┬──────────────────'     β”‚ values                                  β”‚                        β–Ό                                  β”‚      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                  └─No──▢│     value is retired     β”‚                                         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Important: Retained values are held longer than the lifespan of the composable they are associated with. This can cause memory leaks if a retained object is kept beyond its expected lifetime. Be cautious with the types of data that you retain. Never retain an Android Context or an object that references a Context (including View), either directly or indirectly. To mark that a custom class should not be retained (possibly because it will cause a memory leak), you can annotate your class definition with androidx.compose.runtime.annotation.DoNotRetain.

Parameters

keysAn arbitrary list of keys that, if changed, will cause an old retained value to be discarded and for calculation to return a new value, regardless of whether the old value was being retained in the RetainScope or not.
calculationA producer that will be invoked to initialize the retained value if a value from the previous composition isn't available.

Returns

The result of calculation