retain

Functions summary

inline T
@Composable
<T : Any?> retain(noinline calculation: () -> T)

Remember the value produced by calculation and retain it in the LocalRetainedValuesStore.

Cmn
inline T
@Composable
<T : Any?> retain(vararg keys: Any?, noinline calculation: () -> T)

Remember the value produced by calculation and retain it in the LocalRetainedValuesStore.

Cmn

Functions

@Composable
inline fun <T : Any?> retain(noinline calculation: () -> T): T

Remember the value produced by calculation and retain it in the LocalRetainedValuesStore. 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 transiently destroyed 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 the content tracked by a RetainedValuesStore is removed with the expectation that it will be recreated in the future, all of its retained values will be persisted until the content is recreated. If an instance of this function then re-enters the composition hierarchy during this recreation, the retained value will be returned instead of invoking calculation again.

If this function leaves the composition hierarchy when the LocalRetainedValuesStore is not retaining 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 RetainedValuesStore
with the same keys └─────────────────┬─────────┘

┌─Yes────────────────┘ value not
restored and
.──────────────────┴──────────────────. store stops
└─▶( isRetainingExitedValues ) retaining 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.

import androidx.compose.runtime.Composable
import androidx.compose.runtime.retain.retain

@Composable
fun retainMediaPlayer(): MediaPlayer {
    // The returned object will be returned similarly to `remember`.
    // If the composition hierarchy is destroyed and recreated, the same player instance
    // will be returned in the new composition, following the documented retention rules.
    return retain { MediaPlayer() }
}
import androidx.compose.runtime.Composable
import androidx.compose.runtime.RememberObserver
import androidx.compose.runtime.remember
import androidx.compose.runtime.retain.retain
import androidx.compose.runtime.saveable.rememberSerializable

// To combine both retain and rememberSaveable so that an object can be both retained and
// saved, we instead recommend splitting your class into three components: The retained state,
// the saved state, and an object that acts on both.
//
// val saveData = rememberSaveable { ExtractedSaveData() }
// val retainData = retain { ExtractedRetainData() }
// val rememberedAndRetained = remember(saveData, retainData) {
//     Combined(saveData, retainData)
// }
//
// Correctly combining retain with rememberSaveable/rememberSerializable can significantly
// increase the complexity of your state management. We recommend using this pattern when
// building custom architecture patterns, and only when all of the following are true:
//   - You are defining an object comprised of a mix of values that must be retained or saved
//   - Your state is scoped to a Composable and isn’t suitable for the singleton scoping or
//     lifespan of ViewModel
@Composable
fun rememberSearchUiModel(): SearchUiModel {
    val savedModel = rememberSerializable(serializer = serializer()) { SavedSearchUiModel() }
    val retainedModel = retain { RetainedSearchUiModel() }

    // Generally:
    // return remember(savedModel, retainedModel) { SearchUiModel(savedModel, retainedModel) }

    // The retained model needs to initialize if the saved state was restored but the retained
    // instance was recreated. Perform that initialization with an anonymous RememberObserver:
    return remember(savedModel, retainedModel) {
            object : RememberObserver {
                val searchUiModel = SearchUiModel(savedModel, retainedModel)

                override fun onRemembered() {
                    searchUiModel.initialize()
                }

                override fun onForgotten() {}

                override fun onAbandoned() {}
            }
        }
        .searchUiModel
}
Parameters
noinline calculation: () -> T

A 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
T

The result of calculation

Throws
IllegalArgumentException

if the return result of calculation both implements RememberObserver and does not also implement RetainObserver

See also
remember
@Composable
inline fun <T : Any?> retain(vararg keys: Any?, noinline calculation: () -> T): T

Remember the value produced by calculation and retain it in the LocalRetainedValuesStore. 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 transiently destroyed 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 the content tracked by a RetainedValuesStore is removed with the expectation that it will be recreated in the future, all of its retained values will be persisted until the content is recreated. If an instance of this function then re-enters the composition hierarchy during this recreation, the retained value will be returned instead of invoking calculation again.

If this function leaves the composition hierarchy when the LocalRetainedValuesStore is not retaining 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 invalid 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 RetainedValuesStore
with the same keys └─────────────────┬─────────┘

┌─Yes────────────────┘ value not
restored and
.──────────────────┴──────────────────. store stops
└─▶( isRetainingExitedValues ) retaining 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.

Because keys are held for the same duration as retained values, all input keys must follow the same lifespan requirements to prevent a memory leak. Do not use a key that references objects like Context or View. Types annotated with androidx.compose.runtime.annotation.DoNotRetain are similarly flagged as an error when used as a key to retain.

import androidx.compose.runtime.Composable
import androidx.compose.runtime.retain.retain

@Composable
fun retainMediaPlayer(): MediaPlayer {
    // The returned object will be returned similarly to `remember`.
    // If the composition hierarchy is destroyed and recreated, the same player instance
    // will be returned in the new composition, following the documented retention rules.
    return retain { MediaPlayer() }
}
import androidx.compose.runtime.Composable
import androidx.compose.runtime.RememberObserver
import androidx.compose.runtime.remember
import androidx.compose.runtime.retain.retain
import androidx.compose.runtime.saveable.rememberSerializable

// To combine both retain and rememberSaveable so that an object can be both retained and
// saved, we instead recommend splitting your class into three components: The retained state,
// the saved state, and an object that acts on both.
//
// val saveData = rememberSaveable { ExtractedSaveData() }
// val retainData = retain { ExtractedRetainData() }
// val rememberedAndRetained = remember(saveData, retainData) {
//     Combined(saveData, retainData)
// }
//
// Correctly combining retain with rememberSaveable/rememberSerializable can significantly
// increase the complexity of your state management. We recommend using this pattern when
// building custom architecture patterns, and only when all of the following are true:
//   - You are defining an object comprised of a mix of values that must be retained or saved
//   - Your state is scoped to a Composable and isn’t suitable for the singleton scoping or
//     lifespan of ViewModel
@Composable
fun rememberSearchUiModel(): SearchUiModel {
    val savedModel = rememberSerializable(serializer = serializer()) { SavedSearchUiModel() }
    val retainedModel = retain { RetainedSearchUiModel() }

    // Generally:
    // return remember(savedModel, retainedModel) { SearchUiModel(savedModel, retainedModel) }

    // The retained model needs to initialize if the saved state was restored but the retained
    // instance was recreated. Perform that initialization with an anonymous RememberObserver:
    return remember(savedModel, retainedModel) {
            object : RememberObserver {
                val searchUiModel = SearchUiModel(savedModel, retainedModel)

                override fun onRemembered() {
                    searchUiModel.initialize()
                }

                override fun onForgotten() {}

                override fun onAbandoned() {}
            }
        }
        .searchUiModel
}
Parameters
vararg keys: Any?

An 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 RetainedValuesStore or not.

noinline calculation: () -> T

A producer that will be invoked to initialize the retained value if a value from the previous composition isn't available.

Returns
T

The result of calculation

Throws
IllegalArgumentException

if the return result of calculation both implements RememberObserver and does not also implement RetainObserver

See also
remember