androidx.lifecycle.viewmodel.compose

Objects

LocalViewModelStoreOwner

The CompositionLocal containing the current ViewModelStoreOwner.

Cmn

Annotations

Top-level functions summary

inline VM
@Composable
<VM : ViewModel> viewModel(
    viewModelStoreOwner: ViewModelStoreOwner,
    key: String?,
    noinline initializer: CreationExtras.() -> VM
)

Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or an activity)

Cmn
inline VM
@Composable
<VM : ViewModel> viewModel(
    viewModelStoreOwner: ViewModelStoreOwner,
    key: String?,
    factory: ViewModelProvider.Factory?,
    extras: CreationExtras
)

Returns an existing ViewModel or creates a new one in the given owner (usually, a fragment or an activity), defaulting to the owner provided by LocalViewModelStoreOwner.

Cmn
VM
@Composable
<VM : ViewModel> viewModel(
    modelClass: Class<VM>,
    viewModelStoreOwner: ViewModelStoreOwner,
    key: String?,
    factory: ViewModelProvider.Factory?,
    extras: CreationExtras
)

Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or an activity)

android
VM
@Composable
<VM : ViewModel> viewModel(
    modelClass: KClass<VM>,
    viewModelStoreOwner: ViewModelStoreOwner,
    key: String?,
    factory: ViewModelProvider.Factory?,
    extras: CreationExtras
)

Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or an activity)

Cmn

Extension functions summary

PropertyDelegateProvider<Any?, ReadOnlyProperty<Any?, T>>
@SavedStateHandleSaveableApi
<T : Any> SavedStateHandle.saveable(saver: Saver<T, Any>, init: () -> T)

Inter-opt between SavedStateHandle and Saver so that any state holder that is being saved via rememberSaveable with a custom Saver can also be saved with SavedStateHandle.

android
PropertyDelegateProvider<Any?, ReadWriteProperty<Any?, T>>
@SavedStateHandleSaveableApi
<T : Any?, M : MutableState<T>> SavedStateHandle.saveable(
    stateSaver: Saver<T, Any>,
    init: () -> M
)

Inter-opt between SavedStateHandle and Saver so that any state holder that is being saved via rememberSaveable with a custom Saver can also be saved with SavedStateHandle.

android
T
@SavedStateHandleSaveableApi
<T : Any> SavedStateHandle.saveable(
    key: String,
    saver: Saver<T, Any>,
    init: () -> T
)

Inter-opt between SavedStateHandle and Saver so that any state holder that is being saved via rememberSaveable with a custom Saver can also be saved with SavedStateHandle.

android
MutableState<T>
@SavedStateHandleSaveableApi
<T : Any?> SavedStateHandle.saveable(
    key: String,
    stateSaver: Saver<T, Any>,
    init: () -> MutableState<T>
)

Inter-opt between SavedStateHandle and Saver so that any state holder that is being saved via rememberSaveable with a custom Saver can also be saved with SavedStateHandle.

android

Top-level functions

@Composable
inline fun <VM : ViewModel> viewModel(
    viewModelStoreOwner: ViewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) { "No ViewModelStoreOwner was provided via LocalViewModelStoreOwner" },
    key: String? = null,
    noinline initializer: CreationExtras.() -> VM
): VM

Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or an activity)

The created ViewModel is associated with the given viewModelStoreOwner and will be retained as long as the scope is alive (e.g. if it is an activity, until it is finished or process is killed).

If the viewModelStoreOwner implements HasDefaultViewModelProviderFactory its default CreationExtras are the ones that will be provided to the receiver scope from the initializer

import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.createSavedStateHandle
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
import androidx.lifecycle.viewmodel.compose.viewModel

// Just like any call to viewModel(), the default owner is the LocalViewModelStoreOwner.current.
// The lambda is only called the first time the ViewModel needs to be created.
val viewModel = viewModel {
    // Within the lambda, you have direct access to the CreationExtras which allows you to call
    // extension methods on CreationExtras such as createSavedStateHandle()
    val handle = createSavedStateHandle()
    // You can send any custom parameter, repository, etc. to your ViewModel.
    SavedStateViewModel(handle, "custom_value")
}
// The handle and parameter are now available from the ViewModel
viewModel.handle
viewModel.value
Parameters
viewModelStoreOwner: ViewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) { "No ViewModelStoreOwner was provided via LocalViewModelStoreOwner" }

The scope that the created ViewModel should be associated with.

key: String? = null

The key to use to identify the ViewModel.

noinline initializer: CreationExtras.() -> VM

lambda used to create an instance of the ViewModel class

Returns
VM

A ViewModel that is an instance of the given VM type.

@Composable
inline fun <VM : ViewModel> viewModel(
    viewModelStoreOwner: ViewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) { "No ViewModelStoreOwner was provided via LocalViewModelStoreOwner" },
    key: String? = null,
    factory: ViewModelProvider.Factory? = null,
    extras: CreationExtras = if (viewModelStoreOwner is HasDefaultViewModelProviderFactory) { viewModelStoreOwner.defaultViewModelCreationExtras } else { CreationExtras.Empty }
): VM

Returns an existing ViewModel or creates a new one in the given owner (usually, a fragment or an activity), defaulting to the owner provided by LocalViewModelStoreOwner.

The created ViewModel is associated with the given viewModelStoreOwner and will be retained as long as the owner is alive (e.g. if it is an activity, until it is finished or process is killed).

If default arguments are provided via the CreationExtras, they will be available to the appropriate factory when the ViewModel is created.

import androidx.compose.runtime.remember
import androidx.core.os.bundleOf
import androidx.lifecycle.DEFAULT_ARGS_KEY
import androidx.lifecycle.HasDefaultViewModelProviderFactory
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.MutableCreationExtras
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
import androidx.lifecycle.viewmodel.compose.viewModel

val owner = LocalViewModelStoreOwner.current
val defaultExtras =
    (owner as? HasDefaultViewModelProviderFactory)?.defaultViewModelCreationExtras
        ?: CreationExtras.Empty
// Custom extras should always be added on top of the default extras
val extras = MutableCreationExtras(defaultExtras)
extras[DEFAULT_ARGS_KEY] = bundleOf("test" to "my_value")
// This factory is normally created separately and passed in
val customFactory = remember {
    object : ViewModelProvider.Factory {
        override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
            val args = extras[DEFAULT_ARGS_KEY]?.getString("test")
            @Suppress("UNCHECKED_CAST")
            // TestViewModel is a basic ViewModel that sets a String variable
            return TestViewModel(args) as T
        }
    }
}
// Create a ViewModel using the custom factory passing in the custom extras
val viewModel = customFactory.create(TestViewModel::class.java, extras)
// The value from the extras is now available in the ViewModel
viewModel.args
Parameters
viewModelStoreOwner: ViewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) { "No ViewModelStoreOwner was provided via LocalViewModelStoreOwner" }

The owner of the ViewModel that controls the scope and lifetime of the returned ViewModel. Defaults to using LocalViewModelStoreOwner.

key: String? = null

The key to use to identify the ViewModel.

factory: ViewModelProvider.Factory? = null

The ViewModelProvider.Factory that should be used to create the ViewModel or null if you would like to use the default factory from the LocalViewModelStoreOwner

extras: CreationExtras = if (viewModelStoreOwner is HasDefaultViewModelProviderFactory) { viewModelStoreOwner.defaultViewModelCreationExtras } else { CreationExtras.Empty }

The default extras used to create the ViewModel.

Returns
VM

A ViewModel that is an instance of the given VM type.

@Composable
fun <VM : ViewModel> viewModel(
    modelClass: Class<VM>,
    viewModelStoreOwner: ViewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) { "No ViewModelStoreOwner was provided via LocalViewModelStoreOwner" },
    key: String? = null,
    factory: ViewModelProvider.Factory? = null,
    extras: CreationExtras = if (viewModelStoreOwner is HasDefaultViewModelProviderFactory) { viewModelStoreOwner.defaultViewModelCreationExtras } else { CreationExtras.Empty }
): VM

Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or an activity)

The created ViewModel is associated with the given viewModelStoreOwner and will be retained as long as the scope is alive (e.g. if it is an activity, until it is finished or process is killed).

If default arguments are provided via the CreationExtras, they will be available to the appropriate factory when the ViewModel is created.

import androidx.compose.runtime.remember
import androidx.core.os.bundleOf
import androidx.lifecycle.DEFAULT_ARGS_KEY
import androidx.lifecycle.HasDefaultViewModelProviderFactory
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.MutableCreationExtras
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
import androidx.lifecycle.viewmodel.compose.viewModel

val owner = LocalViewModelStoreOwner.current
val defaultExtras =
    (owner as? HasDefaultViewModelProviderFactory)?.defaultViewModelCreationExtras
        ?: CreationExtras.Empty
// Custom extras should always be added on top of the default extras
val extras = MutableCreationExtras(defaultExtras)
extras[DEFAULT_ARGS_KEY] = bundleOf("test" to "my_value")
// This factory is normally created separately and passed in
val customFactory = remember {
    object : ViewModelProvider.Factory {
        override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
            val args = extras[DEFAULT_ARGS_KEY]?.getString("test")
            @Suppress("UNCHECKED_CAST")
            // TestViewModel is a basic ViewModel that sets a String variable
            return TestViewModel(args) as T
        }
    }
}
// Create a ViewModel using the custom factory passing in the custom extras
val viewModel = customFactory.create(TestViewModel::class.java, extras)
// The value from the extras is now available in the ViewModel
viewModel.args
Parameters
modelClass: Class<VM>

The class of the ViewModel to create an instance of it if it is not present.

viewModelStoreOwner: ViewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) { "No ViewModelStoreOwner was provided via LocalViewModelStoreOwner" }

The scope that the created ViewModel should be associated with.

key: String? = null

The key to use to identify the ViewModel.

factory: ViewModelProvider.Factory? = null

The ViewModelProvider.Factory that should be used to create the ViewModel or null if you would like to use the default factory from the LocalViewModelStoreOwner

extras: CreationExtras = if (viewModelStoreOwner is HasDefaultViewModelProviderFactory) { viewModelStoreOwner.defaultViewModelCreationExtras } else { CreationExtras.Empty }

The default extras used to create the ViewModel.

Returns
VM

A ViewModel that is an instance of the given VM type.

@Composable
fun <VM : ViewModel> viewModel(
    modelClass: KClass<VM>,
    viewModelStoreOwner: ViewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) { "No ViewModelStoreOwner was provided via LocalViewModelStoreOwner" },
    key: String? = null,
    factory: ViewModelProvider.Factory? = null,
    extras: CreationExtras = if (viewModelStoreOwner is HasDefaultViewModelProviderFactory) { viewModelStoreOwner.defaultViewModelCreationExtras } else { CreationExtras.Empty }
): VM

Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or an activity)

The created ViewModel is associated with the given viewModelStoreOwner and will be retained as long as the scope is alive (e.g. if it is an activity, until it is finished or process is killed).

If default arguments are provided via the CreationExtras, they will be available to the appropriate factory when the ViewModel is created.

import androidx.compose.runtime.remember
import androidx.core.os.bundleOf
import androidx.lifecycle.DEFAULT_ARGS_KEY
import androidx.lifecycle.HasDefaultViewModelProviderFactory
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.MutableCreationExtras
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
import androidx.lifecycle.viewmodel.compose.viewModel

val owner = LocalViewModelStoreOwner.current
val defaultExtras =
    (owner as? HasDefaultViewModelProviderFactory)?.defaultViewModelCreationExtras
        ?: CreationExtras.Empty
// Custom extras should always be added on top of the default extras
val extras = MutableCreationExtras(defaultExtras)
extras[DEFAULT_ARGS_KEY] = bundleOf("test" to "my_value")
// This factory is normally created separately and passed in
val customFactory = remember {
    object : ViewModelProvider.Factory {
        override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
            val args = extras[DEFAULT_ARGS_KEY]?.getString("test")
            @Suppress("UNCHECKED_CAST")
            // TestViewModel is a basic ViewModel that sets a String variable
            return TestViewModel(args) as T
        }
    }
}
// Create a ViewModel using the custom factory passing in the custom extras
val viewModel = customFactory.create(TestViewModel::class.java, extras)
// The value from the extras is now available in the ViewModel
viewModel.args
Parameters
modelClass: KClass<VM>

The class of the ViewModel to create an instance of it if it is not present.

viewModelStoreOwner: ViewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) { "No ViewModelStoreOwner was provided via LocalViewModelStoreOwner" }

The scope that the created ViewModel should be associated with.

key: String? = null

The key to use to identify the ViewModel.

factory: ViewModelProvider.Factory? = null

The ViewModelProvider.Factory that should be used to create the ViewModel or null if you would like to use the default factory from the LocalViewModelStoreOwner

extras: CreationExtras = if (viewModelStoreOwner is HasDefaultViewModelProviderFactory) { viewModelStoreOwner.defaultViewModelCreationExtras } else { CreationExtras.Empty }

The default extras used to create the ViewModel.

Returns
VM

A ViewModel that is an instance of the given VM type.

Extension functions

@SavedStateHandleSaveableApi
fun <T : Any> SavedStateHandle.saveable(
    saver: Saver<T, Any> = autoSaver(),
    init: () -> T
): PropertyDelegateProvider<Any?, ReadOnlyProperty<Any?, T>>

Inter-opt between SavedStateHandle and Saver so that any state holder that is being saved via rememberSaveable with a custom Saver can also be saved with SavedStateHandle.

The key is automatically retrieved as the name of the property this delegate is being used to create.

The returned state T should be the only way that a value is saved or restored from the SavedStateHandle with the automatic key.

Using the same key again with another SavedStateHandle method is not supported, as values won't cross-set or communicate updates.

import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.listSaver
import androidx.compose.runtime.toMutableStateList
import androidx.compose.runtime.toMutableStateMap
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.SavedStateHandleSaveableApi
import androidx.lifecycle.viewmodel.compose.saveable

/** A simple item that is not inherently [Parcelable] */
data class Item(val id: UUID, val value: String)

@OptIn(SavedStateHandleSaveableApi::class)
class SnapshotStateViewModel(handle: SavedStateHandle) : ViewModel() {

    /**
     * A snapshot-backed [MutableList] of a list of items, persisted by the [SavedStateHandle].
     * The size of this set must remain small in expectation, since the maximum size of saved
     * instance state space is limited.
     */
    private val items: MutableList<Item> by
        handle.saveable(
            saver =
                listSaver(
                    save = { it.map { item -> listOf(item.id.toString(), item.value) } },
                    restore = {
                        it.map { saved ->
                                Item(id = UUID.fromString(saved[0]), value = saved[1])
                            }
                            .toMutableStateList()
                    }
                )
        ) {
            mutableStateListOf()
        }

    /**
     * A snapshot-backed [MutableMap] representing a set of selected item ids, persisted by the
     * [SavedStateHandle]. A [MutableSet] is approximated by ignoring the keys. The size of this
     * set must remain small in expectation, since the maximum size of saved instance state
     * space is limited.
     */
    private val selectedItemIds: MutableMap<UUID, Unit> by
        handle.saveable(
            saver =
                listSaver(
                    save = { it.keys.map(UUID::toString) },
                    restore = {
                        it.map(UUID::fromString).map { id -> id to Unit }.toMutableStateMap()
                    }
                )
        ) {
            mutableStateMapOf()
        }

    /**
     * A snapshot-backed flag representing where selections are enabled, persisted by the
     * [SavedStateHandle].
     */
    var areSelectionsEnabled by handle.saveable { mutableStateOf(true) }

    /** A list of items paired with a selection state. */
    val selectedItems: List<Pair<Item, Boolean>>
        get() = items.map { it to (it.id in selectedItemIds) }

    /** Updates the selection state for the item with [id] to [selected]. */
    fun selectItem(id: UUID, selected: Boolean) {
        if (selected) {
            selectedItemIds[id] = Unit
        } else {
            selectedItemIds.remove(id)
        }
    }

    /** Adds an item with the given [value]. */
    fun addItem(value: String) {
        items.add(Item(UUID.randomUUID(), value))
    }
}
@SavedStateHandleSaveableApi
fun <T : Any?, M : MutableState<T>> SavedStateHandle.saveable(
    stateSaver: Saver<T, Any> = autoSaver(),
    init: () -> M
): PropertyDelegateProvider<Any?, ReadWriteProperty<Any?, T>>

Inter-opt between SavedStateHandle and Saver so that any state holder that is being saved via rememberSaveable with a custom Saver can also be saved with SavedStateHandle.

The key is automatically retrieved as the name of the property this delegate is being used to create.

The delegated MutableState should be the only way that a value is saved or restored from the SavedStateHandle with the automatic key.

Using the same key again with another SavedStateHandle method is not supported, as values won't cross-set or communicate updates.

Use this overload to allow delegating to a mutable state just like you can with rememberSaveable:

var value by savedStateHandle.saveable { mutableStateOf("initialValue") }
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.listSaver
import androidx.compose.runtime.toMutableStateList
import androidx.compose.runtime.toMutableStateMap
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.SavedStateHandleSaveableApi
import androidx.lifecycle.viewmodel.compose.saveable

/** A simple item that is not inherently [Parcelable] */
data class Item(val id: UUID, val value: String)

@OptIn(SavedStateHandleSaveableApi::class)
class SnapshotStateViewModel(handle: SavedStateHandle) : ViewModel() {

    /**
     * A snapshot-backed [MutableList] of a list of items, persisted by the [SavedStateHandle].
     * The size of this set must remain small in expectation, since the maximum size of saved
     * instance state space is limited.
     */
    private val items: MutableList<Item> by
        handle.saveable(
            saver =
                listSaver(
                    save = { it.map { item -> listOf(item.id.toString(), item.value) } },
                    restore = {
                        it.map { saved ->
                                Item(id = UUID.fromString(saved[0]), value = saved[1])
                            }
                            .toMutableStateList()
                    }
                )
        ) {
            mutableStateListOf()
        }

    /**
     * A snapshot-backed [MutableMap] representing a set of selected item ids, persisted by the
     * [SavedStateHandle]. A [MutableSet] is approximated by ignoring the keys. The size of this
     * set must remain small in expectation, since the maximum size of saved instance state
     * space is limited.
     */
    private val selectedItemIds: MutableMap<UUID, Unit> by
        handle.saveable(
            saver =
                listSaver(
                    save = { it.keys.map(UUID::toString) },
                    restore = {
                        it.map(UUID::fromString).map { id -> id to Unit }.toMutableStateMap()
                    }
                )
        ) {
            mutableStateMapOf()
        }

    /**
     * A snapshot-backed flag representing where selections are enabled, persisted by the
     * [SavedStateHandle].
     */
    var areSelectionsEnabled by handle.saveable { mutableStateOf(true) }

    /** A list of items paired with a selection state. */
    val selectedItems: List<Pair<Item, Boolean>>
        get() = items.map { it to (it.id in selectedItemIds) }

    /** Updates the selection state for the item with [id] to [selected]. */
    fun selectItem(id: UUID, selected: Boolean) {
        if (selected) {
            selectedItemIds[id] = Unit
        } else {
            selectedItemIds.remove(id)
        }
    }

    /** Adds an item with the given [value]. */
    fun addItem(value: String) {
        items.add(Item(UUID.randomUUID(), value))
    }
}
@SavedStateHandleSaveableApi
fun <T : Any> SavedStateHandle.saveable(
    key: String,
    saver: Saver<T, Any> = autoSaver(),
    init: () -> T
): T

Inter-opt between SavedStateHandle and Saver so that any state holder that is being saved via rememberSaveable with a custom Saver can also be saved with SavedStateHandle.

The returned state T should be the only way that a value is saved or restored from the SavedStateHandle with the given key.

Using the same key again with another SavedStateHandle method is not supported, as values won't cross-set or communicate updates.

import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.listSaver
import androidx.compose.runtime.toMutableStateList
import androidx.compose.runtime.toMutableStateMap
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.SavedStateHandleSaveableApi
import androidx.lifecycle.viewmodel.compose.saveable

/** A simple item that is not inherently [Parcelable] */
data class Item(val id: UUID, val value: String)

@OptIn(SavedStateHandleSaveableApi::class)
class SnapshotStateViewModel(handle: SavedStateHandle) : ViewModel() {

    /**
     * A snapshot-backed [MutableList] of a list of items, persisted by the [SavedStateHandle].
     * The size of this set must remain small in expectation, since the maximum size of saved
     * instance state space is limited.
     */
    private val items: MutableList<Item> =
        handle.saveable(
            key = "items",
            saver =
                listSaver(
                    save = { it.map { item -> listOf(item.id.toString(), item.value) } },
                    restore = {
                        it.map { saved ->
                                Item(id = UUID.fromString(saved[0]), value = saved[1])
                            }
                            .toMutableStateList()
                    }
                )
        ) {
            mutableStateListOf()
        }

    /**
     * A snapshot-backed [MutableMap] representing a set of selected item ids, persisted by the
     * [SavedStateHandle]. A [MutableSet] is approximated by ignoring the keys. The size of this
     * set must remain small in expectation, since the maximum size of saved instance state
     * space is limited.
     */
    private val selectedItemIds: MutableMap<UUID, Unit> =
        handle.saveable(
            key = "selectedItemIds",
            saver =
                listSaver(
                    save = { it.keys.map(UUID::toString) },
                    restore = {
                        it.map(UUID::fromString).map { id -> id to Unit }.toMutableStateMap()
                    }
                )
        ) {
            mutableStateMapOf()
        }

    /**
     * A snapshot-backed flag representing where selections are enabled, persisted by the
     * [SavedStateHandle].
     */
    var areSelectionsEnabled by handle.saveable("areSelectionsEnabled") { mutableStateOf(true) }

    /** A list of items paired with a selection state. */
    val selectedItems: List<Pair<Item, Boolean>>
        get() = items.map { it to (it.id in selectedItemIds) }

    /** Updates the selection state for the item with [id] to [selected]. */
    fun selectItem(id: UUID, selected: Boolean) {
        if (selected) {
            selectedItemIds[id] = Unit
        } else {
            selectedItemIds.remove(id)
        }
    }

    /** Adds an item with the given [value]. */
    fun addItem(value: String) {
        items.add(Item(UUID.randomUUID(), value))
    }
}
@SavedStateHandleSaveableApi
fun <T : Any?> SavedStateHandle.saveable(
    key: String,
    stateSaver: Saver<T, Any>,
    init: () -> MutableState<T>
): MutableState<T>

Inter-opt between SavedStateHandle and Saver so that any state holder that is being saved via rememberSaveable with a custom Saver can also be saved with SavedStateHandle.

The returned MutableState should be the only way that a value is saved or restored from the SavedStateHandle with the given key.

Using the same key again with another SavedStateHandle method is not supported, as values won't cross-set or communicate updates.

Use this overload if you remember a mutable state with a type which can't be stored in the Bundle so you have to provide a custom saver object.

import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.listSaver
import androidx.compose.runtime.toMutableStateList
import androidx.compose.runtime.toMutableStateMap
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.SavedStateHandleSaveableApi
import androidx.lifecycle.viewmodel.compose.saveable

/** A simple item that is not inherently [Parcelable] */
data class Item(val id: UUID, val value: String)

@OptIn(SavedStateHandleSaveableApi::class)
class SnapshotStateViewModel(handle: SavedStateHandle) : ViewModel() {

    /**
     * A snapshot-backed [MutableList] of a list of items, persisted by the [SavedStateHandle].
     * The size of this set must remain small in expectation, since the maximum size of saved
     * instance state space is limited.
     */
    private val items: MutableList<Item> =
        handle.saveable(
            key = "items",
            saver =
                listSaver(
                    save = { it.map { item -> listOf(item.id.toString(), item.value) } },
                    restore = {
                        it.map { saved ->
                                Item(id = UUID.fromString(saved[0]), value = saved[1])
                            }
                            .toMutableStateList()
                    }
                )
        ) {
            mutableStateListOf()
        }

    /**
     * A snapshot-backed [MutableMap] representing a set of selected item ids, persisted by the
     * [SavedStateHandle]. A [MutableSet] is approximated by ignoring the keys. The size of this
     * set must remain small in expectation, since the maximum size of saved instance state
     * space is limited.
     */
    private val selectedItemIds: MutableMap<UUID, Unit> =
        handle.saveable(
            key = "selectedItemIds",
            saver =
                listSaver(
                    save = { it.keys.map(UUID::toString) },
                    restore = {
                        it.map(UUID::fromString).map { id -> id to Unit }.toMutableStateMap()
                    }
                )
        ) {
            mutableStateMapOf()
        }

    /**
     * A snapshot-backed flag representing where selections are enabled, persisted by the
     * [SavedStateHandle].
     */
    var areSelectionsEnabled by handle.saveable("areSelectionsEnabled") { mutableStateOf(true) }

    /** A list of items paired with a selection state. */
    val selectedItems: List<Pair<Item, Boolean>>
        get() = items.map { it to (it.id in selectedItemIds) }

    /** Updates the selection state for the item with [id] to [selected]. */
    fun selectItem(id: UUID, selected: Boolean) {
        if (selected) {
            selectedItemIds[id] = Unit
        } else {
            selectedItemIds.remove(id)
        }
    }

    /** Adds an item with the given [value]. */
    fun addItem(value: String) {
        items.add(Item(UUID.randomUUID(), value))
    }
}