RetainedValuesStoreRegistry


A RetainedValuesStoreRegistry creates and manages RetainedValuesStore instances for collections of items. This is desirable for components that swap in and out children where each child should be able to retain values when it becomes removed from the composition hierarchy.

To use this class, call getOrCreateRetainedValuesStoreForChild to instantiate the RetainedValuesStore that should be installed for a given child content block. For automatic installation and content tracking, wrap your content in ProvideChildRetainedValuesStore.

You can also install the managed RetainedValuesStores manually by obtaining a RetainedValuesStore with getOrCreateRetainedValuesStoreForChild and setting it as the LocalRetainedValuesStore for your children's content. When a child is being removed, call startRetainingExitedValues to begin the transient destruction phase of your retention scenario. After the child has been added back to the composition, invoke stopRetainingExitedValues to finalize the restoration of retained values.

When a RetainedValuesStoreRegistry is no longer used, you must call dispose before the provider is garbage collected. This ensures that all retained values are correctly retired. Failure to do so may result in leaked memory from undispatched RetainObserver.onRetired callbacks. Instances created by retainRetainedValuesStoreRegistry are automatically disposed when the provider stops being retained.

import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.retain.retain
import androidx.compose.runtime.retain.retainRetainedValuesStoreRegistry
import androidx.compose.ui.graphics.painter.Painter

// List item that retains a value
@Composable
fun Contact(contact: Contact) {
    Row {
        // Retain this painter to cache the contact icon in memory
        val contactIcon = retain { ContactIconPainter() }
        Image(contactIcon, "Contact icon")
        Text(contact.name)
    }
}

@Composable
fun ContactsList(contacts: List<Contact>) {
    // Create the RetainedValuesStoreRegistry
    val retainedValuesStoreRegistry = retainRetainedValuesStoreRegistry()
    LazyColumn {
        items(contacts) { contact ->
            // Install it for an item in a list
            retainedValuesStoreRegistry.ProvideChildRetainedValuesStore(contact.id) {
                // This contact now gets its own retain store.
                // If the store of ContactsList starts retaining exited values, this nested
                // store will too. If this contact leaves re-enters composition, it will keep
                // its previously retained values.
                Contact(contact)
            }
        }
    }

    // Optional: Purge child stores if a contact gets deleted.
    DisposableEffect(contacts) {
        val contactIdsSet = contacts.map { it.id }
        retainedValuesStoreRegistry.clearChildren { it !in contactIdsSet }
        onDispose {}
    }
}

Summary

Public constructors

Cmn

Public functions

Unit

Installs child content that should be retained under the given key.

Cmn
Unit
clearChild(key: Any?)

Removes the RetainedValuesStore for the child with the given key from this RetainedValuesStoreRegistry.

Cmn
Unit
clearChildren(predicate: (Any?) -> Boolean)

Bulk removes all child stores for which the predicate returns true.

Cmn
Unit

Removes all child RetainedValuesStores from this RetainedValuesStoreRegistry and marks it as ineligible for future use.

Cmn
RetainedValuesStore

Creates or returns a previously created RetainedValuesStore instance for the given key.

Cmn
Int

Gets the total number of active requests from startRetainingExitedValues for the given key.

Cmn
Unit

When a RetainStateProvider is set as the parent of a RetainedValuesStoreRegistry, the RetainedValuesStoreRegistry will mirror the retention state of the parent.

Cmn
Unit

Starts retaining exited values for a child with the given key.

Cmn
Unit

Stops retaining exited values for a child with the given key as previously started by startRetainingExitedValues.

Cmn

Public constructors

RetainedValuesStoreRegistry

RetainedValuesStoreRegistry()

Public functions

ProvideChildRetainedValuesStore

@Composable
fun ProvideChildRetainedValuesStore(key: Any?, content: @Composable () -> Unit): Unit

Installs child content that should be retained under the given key. startRetainingExitedValues and stopRetainingExitedValues and automatically called based on the presence of this composable for the key.

When removed, this composable begins retaining exited values from the content lambda under the given key. When added back to the composition hierarchy, the store will stop retaining exited values once the composition completes and will release any unused values. The keys used with this method should only be used once per RetainedValuesStoreRegistry in a composition.

This composable only attempts to manage the retention lifecycle for the content and key pair. It will retain removed content indefinitely until clearChild or clearChildren is invoked.

Parameters
key: Any?

The child key associated with the given content. This key is used to identify the retention pool for objects retained by the content composable.

content: @Composable () -> Unit

The composable content to compose with the RetainedValuesStore of the given key

Throws
kotlin.IllegalStateException

if dispose has been called

clearChild

fun clearChild(key: Any?): Unit

Removes the RetainedValuesStore for the child with the given key from this RetainedValuesStoreRegistry. If the key doesn't have an associated RetainedValuesStore yet (either because it hasn't been created or has already been cleared), this function does nothing.

If the store being cleared is currently retaining exited values, it will stop as a result of this call. If a child with the given key is currently in the composition hierarchy, its retained values will not be persisted the next time the child content is destroyed. Orphaned RetainedValuesStores will never begin retaining exited values, and the content will need to be recreated with a new RetainedValuesStore before exited values will be kept again.

If getOrCreateRetainedValuesStoreForChild is called again for the given key, a new RetainedValuesStore will be created and returned.

Parameters
key: Any?

The key of the child content whose RetainedValuesStore should be discarded

clearChildren

fun clearChildren(predicate: (Any?) -> Boolean): Unit

Bulk removes all child stores for which the predicate returns true. This function follows the same clearing rules as clearChild.

Parameters
predicate: (Any?) -> Boolean

The predicate to evaluate on all child keys in the RetainedValuesStoreRegistry. If the predicate returns true for a given key, it will be cleared. If the predicate returns false it will remain in the collection.

See also
clearChild

dispose

fun dispose(): Unit

Removes all child RetainedValuesStores from this RetainedValuesStoreRegistry and marks it as ineligible for future use. This is required to invoke when the store is no longer used to retire any retained values. Failing to do so may result in memory leaks from undispatched RetainObserver.onRetired and RetainedEffect callbacks. When this function is called, all values retained in stores managed by this provider will be immediately retired.

If this store has already been disposed, this function will do nothing.

getOrCreateRetainedValuesStoreForChild

fun getOrCreateRetainedValuesStoreForChild(key: Any?): RetainedValuesStore

Creates or returns a previously created RetainedValuesStore instance for the given key. The returned RetainedValuesStore will be managed by this provider. It will begin retaining if the parent retain store starts retaining or if startRetainingExitedValues is called with the same key, and it will stop retaining with the parent retain store ends retaining and there is no startRetainingExitedValues call without a corresponding stopRetainingExitedValues call for the specified key.

The first time this function is called for a given key, a new RetainedValuesStore is created for the key. When this function is called for the same key, it will return the same RetainedValuesStore it originally returned. If a given key's store is cleared, then a new one will be created for it the next time it is requested via this function.

This function must be called before startRetainingExitedValues or stopRetainingExitedValues is called for those two methods to have any effect on the retention state for the given key.

Parameters
key: Any?

The key to return an existing RetainedValuesStore instance for, if one exists, or to create a new instance for

Returns
RetainedValuesStore

A RetainedValuesStore instance suitable to be installed as the LocalRetainedValuesStore for the child content with the specified key

Throws
kotlin.IllegalStateException

if dispose has been called

retainExitedValuesRequestsFor

fun retainExitedValuesRequestsFor(key: Any?): Int

Gets the total number of active requests from startRetainingExitedValues for the given key. Effectively, this is the number of calls to startRetainingExitedValues minus the number of calls to stopRetainingExitedValues for the given key.

This counter resets if clearStore is called for the given key. If the store has not been created for key by getOrCreateRetainedValuesStoreForChild, this function will return 0.

Parameters
key: Any?

the key of the child to look up

Returns
Int

The number of active requests against the given child to retain exited values

setParentRetainStateProvider

fun setParentRetainStateProvider(parent: RetainStateProvider): Unit

When a RetainStateProvider is set as the parent of a RetainedValuesStoreRegistry, the RetainedValuesStoreRegistry will mirror the retention state of the parent. If the parent stops retaining, all children that have started retaining via startRetainingExitedValues will continue being retained after the parent stops retaining.

If this function is called twice, the new parent will replace the old parent. The new parent's state is immediately applied to the child stores.

To clear a parent, call this function and pass in either RetainStateProvider.AlwaysRetainExitedValues or RetainStateProvider.NeverRetainExitedValues depending on whether you want this store to retain exited values in the absence of a parent.

startRetainingExitedValues

fun startRetainingExitedValues(key: Any?): Unit

Starts retaining exited values for a child with the given key. If a RetainedValuesStore has not been created for this key (because getOrCreateRetainedValuesStoreForChild was not called for the key or it has been cleared with clearChild or clearChildren), then this function does nothing. If the RetainedValuesStore for the given key is already retaining exited values, the store will not change states. The number of times this function is called is tracked and must be matched by the same number of calls to stopRetainingExitedValues for the given key before its kept values will be retired.

This function must be called before any content for the associated child is removed from the composition hierarchy.

Parameters
key: Any?

The key of the child to begin retention for

stopRetainingExitedValues

fun stopRetainingExitedValues(key: Any?): Unit

Stops retaining exited values for a child with the given key as previously started by startRetainingExitedValues. If the underlying store is not retaining because startRetainingExitedValues has not been called, this function will throw an exception. If no such retain store exists because it was cleared with clearChild or never created with getOrCreateRetainedValuesStoreForChild, this function will do nothing.

If startRetainingExitedValues has been called more than stopRetainingExitedValues, the store will continue to retain values that have exited the composition until stopRetainingExitedValues has been called the same number of times as startRetainingExitedValues.

This function must be called after the completion of the frame in which the child content is being restored to allow the restored child to re-consume all of its retained values. You can use androidx.compose.runtime.Recomposer.scheduleFrameEndCallback or androidx.compose.runtime.Composer.scheduleFrameEndCallback to insert a sufficient delay.

Parameters
key: Any?

The key of the child to end retention for

Throws
kotlin.IllegalStateException

if startRetainingExitedValues is called more times than stopRetainingExitedValues has been called for the given key