androidx.compose.runtime.retain

Interfaces

RetainObserver

Objects implementing this interface are notified of their usage in retain.

Cmn
RetainedEffectResult

The return type of a built RetainedEffect.

Cmn
RetainedValuesStore

A RetainedValuesStore acts as a storage area for objects being retained.

Cmn

Classes

ManagedRetainedValuesStore

A ManagedRetainedValuesStore is the default implementation of RetainedValuesStore that can be used to define custom retention periods in the composition hierarchy.

Cmn
RetainedEffectScope

Receiver scope for RetainedEffect that offers the onRetire clause that should be the last statement in any call to RetainedEffect.

Cmn
RetainedValuesStoreRegistry

A RetainedValuesStoreRegistry creates and manages RetainedValuesStore instances for collections of items.

Cmn

Objects

ForgetfulRetainedValuesStore

The ForgetfulRetainedValuesStore is an implementation of RetainedValuesStore that is incapable of retaining any exited values.

Cmn

Top-level functions summary

Unit

Installs the given RetainedValuesStore over the provided content such that all values retained in the content lambda are owned by store.

Cmn
Unit

This function is deprecated. RetainedEffect must provide one or more 'key' parameters that define the identity of the RetainedEffect and determine when its previous effect should be disposed and a new effect started for the new key.

Cmn
Unit

A side effect of composition that must run for any new unique value of key1 and must be reversed or cleaned up if key1 changes or if the RetainedEffect permanently leaves composition.

Cmn
Unit

A side effect of composition that must run for any new unique value of keys and must be reversed or cleaned up if keys changes or if the RetainedEffect permanently leaves composition.

Cmn
Unit

A side effect of composition that must run for any new unique value of key1 or key2 and must be reversed or cleaned up if key1 or key2 changes or if the RetainedEffect permanently leaves composition.

Cmn
Unit
@Composable
@NonRestartableComposable
RetainedEffect(
    key1: Any?,
    key2: Any?,
    key3: Any?,
    effect: RetainedEffectScope.() -> RetainedEffectResult
)

A side effect of composition that must run for any new unique value of key1, key2, or key3 and must be reversed or cleaned up if key1, key2, or key3 changes or if the RetainedEffect permanently leaves composition.

Cmn
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
ManagedRetainedValuesStore

Retains a ManagedRetainedValuesStore.

Cmn
RetainedValuesStoreRegistry

Returns a retained instance of a new RetainedValuesStoreRegistry.

Cmn

Top-level properties summary

Top-level functions

LocalRetainedValuesStoreProvider

@Composable
fun LocalRetainedValuesStoreProvider(
    store: RetainedValuesStore,
    content: @Composable () -> Unit
): Unit

Installs the given RetainedValuesStore over the provided content such that all values retained in the content lambda are owned by store. When this provider is removed from composition (and the content is therefore removed with it), the store will be notified to start retaining exited values so that it can persist all retained values at the time the content exits composition.

Note that most RetainedValuesStore implementations can only be provided in one location and composition at a time. Attempting to install the same store twice may lead to an error.

import androidx.compose.runtime.Composable
import androidx.compose.runtime.retain.LocalRetainedValuesStoreProvider
import androidx.compose.runtime.retain.RetainedEffect
import androidx.compose.runtime.retain.retain
import androidx.compose.runtime.retain.retainManagedRetainedValuesStore

@Composable
fun CollapsingMediaPlayer(visible: Boolean) {
    // Important: This retainedValuesStore is created outside of the if statement to ensure
    // that it lives as long as the CollapsingMediaPlayer composable itself.
    val retainedValuesStore = retainManagedRetainedValuesStore()

    // This content is only shown when `visible == true`
    if (visible) {
        LocalRetainedValuesStoreProvider(retainedValuesStore) {
            // Create a media player that will be retained when the CollapsingMediaPlayer is not
            // visible. This component can continue to play audio when the video is hidden.
            val mediaPlayer = retain { MediaPlayer() }
            RetainedEffect(mediaPlayer) {
                mediaPlayer.play()
                onRetire { mediaPlayer.stop() }
            }

            // Render the video component inside the RetainedContentHost.
        }
    }
}
Parameters
store: RetainedValuesStore

The RetainedValuesStore to install as the LocalRetainedValuesStore

content: @Composable () -> Unit

The Composable content that the store will be installed for. This content block is invoked immediately in-place.

@Composable
@NonRestartableComposable
fun RetainedEffect(effect: RetainedEffectScope.() -> RetainedEffectResult): Unit
@Composable
@NonRestartableComposable
fun RetainedEffect(key1: Any?, effect: RetainedEffectScope.() -> RetainedEffectResult): Unit

A side effect of composition that must run for any new unique value of key1 and must be reversed or cleaned up if key1 changes or if the RetainedEffect permanently leaves composition.

A RetainedEffect tracks the lifecycle of retained content. If the current RetainedValuesStore is retaining values because its managed content is being transiently destroyed, the RetainedEffect is kept alive. From this state, the RetainedEffect can either:

  • Be retired because the RetainedValuesStore is destroyed without its content being restored

  • Be retired if the RetainedValuesStore's content re-enters the composition but does not include this RetainedEffect or invokes it with different keys

  • Be restored to the recreated composition hierarchy. In this case, the RetainedEffect does not execute any callbacks.

If a RetainedEffect is removed from the composition hierarchy when the RetainedValuesStore is not retaining exited values, then the scope will immediately be retired and behave like a androidx.compose.runtime.DisposableEffect. Retirement has the same timing guarantees as RetainObserver.onRetired.

A RetainedEffect's key is a value that defines the identity of the RetainedEffect. If a RetainedEffect is recomposed with different keys, a new effect will be created and the previous effect will be retired. If the current RetainedValuesStore is not retaining exited values, the retirement happens before the new effect is started. Otherwise, the prior instance of the effect will continue to be retained for possible restoration until the scope stops retaining exited values.

RetainedEffect may be used to initialize or subscribe to a key and reinitialize when a different key is provided. For example:

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

@Composable
fun VideoPlayer(mediaUri: String) {
    val player = retain(mediaUri) { MediaPlayer(mediaUri) }

    // Initialize each player only once after we retain it.
    // If the uri (and therefore the player) change, we need to dispose the old player
    // and initialize the new one. Likewise, the player needs to be disposed of when
    // it stops being retained.
    RetainedEffect(player) {
        player.initialize()
        onRetire { player.close() }
    }

    // ...
}

A RetainedEffect must include a retire clause as the final statement in its effect block. If your operation does not require disposal it might be a androidx.compose.runtime.SideEffect instead, or a androidx.compose.runtime.LaunchedEffect if it launches a coroutine that should be managed by the composition.

There is guaranteed to be one call to retire for every call to effect. Both effect and retire will always be run on the composition's apply dispatcher and appliers are never run concurrent with themselves, one another, applying changes to the composition tree, or running androidx.compose.runtime.RememberObserver event callbacks.

@Composable
@NonRestartableComposable
fun RetainedEffect(vararg keys: Any?, effect: RetainedEffectScope.() -> RetainedEffectResult): Unit

A side effect of composition that must run for any new unique value of keys and must be reversed or cleaned up if keys changes or if the RetainedEffect permanently leaves composition.

A RetainedEffect tracks the lifecycle of retained content. If the current RetainedValuesStore is retaining values because its managed content is being transiently destroyed, the RetainedEffect is kept alive. From this state, the RetainedEffect can either:

  • Be retired because the RetainedValuesStore is destroyed without its content being restored

  • Be retired if the RetainedValuesStore's content re-enters the composition but does not include this RetainedEffect or invokes it with different keys

  • Be restored to the recreated composition hierarchy. In this case, the RetainedEffect does not execute any callbacks.

If a RetainedEffect is removed from the composition hierarchy when the RetainedValuesStore is not retaining exited values, then the scope will immediately be retired and behave like a RetainedEffect. Retirement has the same timing guarantees as RetainObserver.onRetired.

A RetainedEffect's key is a value that defines the identity of the RetainedEffect. If a RetainedEffect is recomposed with different keys, a new effect will be created and the previous effect will be retired. If the current RetainedValuesStore is not retaining exited values, the retirement happens before the new effect is started. Otherwise, the prior instance of the effect will continue to be retained for possible restoration until the scope stops retaining exited values.

RetainedEffect may be used to initialize or subscribe to a key and reinitialize when a different key is provided. For example:

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

@Composable
fun VideoPlayer(mediaUri: String) {
    val player = retain(mediaUri) { MediaPlayer(mediaUri) }

    // Initialize each player only once after we retain it.
    // If the uri (and therefore the player) change, we need to dispose the old player
    // and initialize the new one. Likewise, the player needs to be disposed of when
    // it stops being retained.
    RetainedEffect(player) {
        player.initialize()
        onRetire { player.close() }
    }

    // ...
}

A RetainedEffect must include a retire clause as the final statement in its effect block. If your operation does not require disposal it might be a androidx.compose.runtime.SideEffect instead, or a androidx.compose.runtime.LaunchedEffect if it launches a coroutine that should be managed by the composition.

There is guaranteed to be one call to retire for every call to effect. Both effect and retire will always be run on the composition's apply dispatcher and appliers are never run concurrent with themselves, one another, applying changes to the composition tree, or running androidx.compose.runtime.RememberObserver event callbacks.

@Composable
@NonRestartableComposable
fun RetainedEffect(key1: Any?, key2: Any?, effect: RetainedEffectScope.() -> RetainedEffectResult): Unit

A side effect of composition that must run for any new unique value of key1 or key2 and must be reversed or cleaned up if key1 or key2 changes or if the RetainedEffect permanently leaves composition.

A RetainedEffect tracks the lifecycle of retained content. If the current RetainedValuesStore is retaining values because its managed content is being transiently destroyed, the RetainedEffect is kept alive. From this state, the RetainedEffect can either:

  • Be retired because the RetainedValuesStore is destroyed without its content being restored

  • Be retired if the RetainedValuesStore's content re-enters the composition but does not include this RetainedEffect or invokes it with different keys

  • Be restored to the recreated composition hierarchy. In this case, the RetainedEffect does not execute any callbacks.

If a RetainedEffect is removed from the composition hierarchy when the RetainedValuesStore is not retaining exited values, then the scope will immediately be retired and behave like a androidx.compose.runtime.DisposableEffect. Retirement has the same timing guarantees as RetainObserver.onRetired.

A RetainedEffect's key is a value that defines the identity of the RetainedEffect. If a RetainedEffect is recomposed with different keys, a new effect will be created and the previous effect will be retired. If the current RetainedValuesStore is not retaining exited values, the retirement happens before the new effect is started. Otherwise, the prior instance of the effect will continue to be retained for possible restoration until the scope stops retaining exited values.

RetainedEffect may be used to initialize or subscribe to a key and reinitialize when a different key is provided. For example:

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

@Composable
fun VideoPlayer(mediaUri: String) {
    val player = retain(mediaUri) { MediaPlayer(mediaUri) }

    // Initialize each player only once after we retain it.
    // If the uri (and therefore the player) change, we need to dispose the old player
    // and initialize the new one. Likewise, the player needs to be disposed of when
    // it stops being retained.
    RetainedEffect(player) {
        player.initialize()
        onRetire { player.close() }
    }

    // ...
}

A RetainedEffect must include a retire clause as the final statement in its effect block. If your operation does not require disposal it might be a androidx.compose.runtime.SideEffect instead, or a androidx.compose.runtime.LaunchedEffect if it launches a coroutine that should be managed by the composition.

There is guaranteed to be one call to retire for every call to effect. Both effect and retire will always be run on the composition's apply dispatcher and appliers are never run concurrent with themselves, one another, applying changes to the composition tree, or running androidx.compose.runtime.RememberObserver event callbacks.

@Composable
@NonRestartableComposable
fun RetainedEffect(
    key1: Any?,
    key2: Any?,
    key3: Any?,
    effect: RetainedEffectScope.() -> RetainedEffectResult
): Unit

A side effect of composition that must run for any new unique value of key1, key2, or key3 and must be reversed or cleaned up if key1, key2, or key3 changes or if the RetainedEffect permanently leaves composition.

A RetainedEffect tracks the lifecycle of retained content. If the current RetainedValuesStore is retaining values because its managed content is being transiently destroyed, the RetainedEffect is kept alive. From this state, the RetainedEffect can either:

  • Be retired because the RetainedValuesStore is destroyed without its content being restored

  • Be retired if the RetainedValuesStore's content re-enters the composition but does not include this RetainedEffect or invokes it with different keys

  • Be restored to the recreated composition hierarchy. In this case, the RetainedEffect does not execute any callbacks.

If a RetainedEffect is removed from the composition hierarchy when the RetainedValuesStore is not retaining exited values, then the scope will immediately be retired and behave like a androidx.compose.runtime.DisposableEffect. Retirement has the same timing guarantees as RetainObserver.onRetired.

A RetainedEffect's key is a value that defines the identity of the RetainedEffect. If a RetainedEffect is recomposed with different keys, a new effect will be created and the previous effect will be retired. If the current RetainedValuesStore is not retaining exited values, the retirement happens before the new effect is started. Otherwise, the prior instance of the effect will continue to be retained for possible restoration until the scope stops retaining exited values.

RetainedEffect may be used to initialize or subscribe to a key and reinitialize when a different key is provided. For example:

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

@Composable
fun VideoPlayer(mediaUri: String) {
    val player = retain(mediaUri) { MediaPlayer(mediaUri) }

    // Initialize each player only once after we retain it.
    // If the uri (and therefore the player) change, we need to dispose the old player
    // and initialize the new one. Likewise, the player needs to be disposed of when
    // it stops being retained.
    RetainedEffect(player) {
        player.initialize()
        onRetire { player.close() }
    }

    // ...
}

A RetainedEffect must include a retire clause as the final statement in its effect block. If your operation does not require disposal it might be a androidx.compose.runtime.SideEffect instead, or a androidx.compose.runtime.LaunchedEffect if it launches a coroutine that should be managed by the composition.

There is guaranteed to be one call to retire for every call to effect. Both effect and retire will always be run on the composition's apply dispatcher and appliers are never run concurrent with themselves, one another, applying changes to the composition tree, or running androidx.compose.runtime.RememberObserver event callbacks.

@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.

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
kotlin.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 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 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.

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
kotlin.IllegalArgumentException

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

See also
remember

retainManagedRetainedValuesStore

@Composable
fun retainManagedRetainedValuesStore(): ManagedRetainedValuesStore

Retains a ManagedRetainedValuesStore. The returned store follows the lifespan defined by retain. When the retained scope is retired, it will be disposed.

Optionally, you can enable and disable retention of exited values on this scope via ManagedRetainedValuesStore.enableRetainingExitedValues and ManagedRetainedValuesStore.disableRetainingExitedValues. (The scope is enabled by default.)

import androidx.compose.animation.AnimatedContent
import androidx.compose.runtime.Composable
import androidx.compose.runtime.retain.LocalRetainedValuesStoreProvider
import androidx.compose.runtime.retain.retain
import androidx.compose.runtime.retain.retainManagedRetainedValuesStore

@Composable
fun RetainedAnimatedContent(active: Boolean, content: @Composable () -> Unit) {
    // Create a RetainedValuesStore. It will be added as a child to the current store and start
    // retaining exited values when the parent does. On Android, this store will implicitly
    // survive and forward retention events caused by configuration changes.
    val retainedValuesStore = retainManagedRetainedValuesStore()
    AnimatedContent(active) { targetState ->
        if (targetState) {
            // Install the RetainedValuesStore over the child content
            LocalRetainedValuesStoreProvider(retainedValuesStore) {
                // Values retained here will be kept when this content is faded out,
                // and restored when the content is added back to the composition.
                content()
            }
        }
    }
}

retainRetainedValuesStoreRegistry

@Composable
fun retainRetainedValuesStoreRegistry(): RetainedValuesStoreRegistry

Returns a retained instance of a new RetainedValuesStoreRegistry. A RetainedValuesStoreRegistry is a container of RetainedValuesStores that allows a parent composable to have children with different retention lifecycles. See RetainedValuesStoreRegistry for more information on how to use this class, including a sample.

When this RetainedValuesStoreRegistry is retired, its child stores will also be retired and the store will be disposed.

Top-level properties

LocalRetainedValuesStore

val LocalRetainedValuesStoreProvidableCompositionLocal<RetainedValuesStore>

The RetainedValuesStore in which retain values will be tracked in. Since a RetainedValuesStore controls retention scenarios and signals when to start and end the retention of objects removed from composition, a composition hierarchy may have several RetainedValuesStores to introduce retention periods to specific pieces of content.

The default implementation is a ForgetfulRetainedValuesStore that causes retain to behave the same as remember. On Android, a lifecycle-aware RetainedValuesStore is installed at the root of the composition that retains values across configuration changes.

If this CompositionLocal is updated, all values previously returned by retain will be adopted to the new store and will follow the new store's retention lifecycle.

Always prefer LocalRetainedValuesStoreProvider to setting this local directly. This local is local is exposed providable as an escape hatch for installing a platform- or library-specific LocalRetainedValuesStore at the root of the hierarchy and for testing custom RetainedValuesStore implementations. Stores installed through this local directly will NOT receive the default calls into RetainedValuesStore.onContentEnteredComposition and RetainedValuesStore.onContentExitComposition provided by LocalRetainedValuesStoreProvider.