A handle to saved state passed down to androidx.lifecycle.ViewModel. You should use SavedStateViewModelFactory if you want to receive this object in ViewModel's constructor.

This is a key-value map that will let you write and retrieve objects to and from the saved state. These values will persist after the process is killed by the system and remain available via the same object.

You can read a value from it via get or observe it via androidx.lifecycle.LiveData returned by getLiveData.

You can write a value to it via set or setting a value to androidx.lifecycle.MutableLiveData returned by getLiveData.

Summary

Nested types

Public constructors

Creates a handle with the empty state.

Cmn
android
SavedStateHandle(initialState: Map<StringAny?>)

Creates a handle with the given initial arguments.

Cmn
android

Public functions

Unit

Clear any SavedStateProvider that was previously set via setSavedStateProvider.

Cmn
android
operator Boolean
Cmn
android
operator T?
@MainThread
<T : Any?> get(key: String)

Returns a value associated with the given key.

Cmn
android
MutableStateFlow<T>
@MainThread
<T : Any?> getMutableStateFlow(key: String, initialValue: T)

Returns a MutableStateFlow that will emit the currently active value associated with the given key.

Cmn
android
StateFlow<T>
@MainThread
<T : Any?> getStateFlow(key: String, initialValue: T)

Returns a StateFlow that will emit the currently active value associated with the given key.

Cmn
android
Set<String>

Returns all keys contained in this SavedStateHandle

Cmn
android
T?
@MainThread
<T : Any?> remove(key: String)

Removes a value associated with the given key.

Cmn
android
operator Unit
@MainThread
<T : Any?> set(key: String, value: T?)

Associate the given value with the key.

Cmn
android
Unit

Set a SavedStateProvider that will have its state saved into this SavedStateHandle.

Cmn
android

Extension functions

inline ReadWriteProperty<Any?, T>
<T : Any> SavedStateHandle.saved(noinline init: () -> T)

Returns a property delegate that uses SavedStateHandle to save and restore a value of type T with fully qualified property or variable name as key and the default serializer.

Cmn
inline ReadWriteProperty<Any?, T>
<T : Any> SavedStateHandle.saved(key: String, noinline init: () -> T)

Returns a property delegate that uses SavedStateHandle to save and restore a value of type T with the default serializer.

Cmn
ReadWriteProperty<Any?, T>
<T : Any> SavedStateHandle.saved(
    serializer: <Error class: unknown class><T>,
    init: () -> T
)

Returns a property delegate that uses SavedStateHandle to save and restore a value of type T with fully qualified property or variable name as key.

Cmn
ReadWriteProperty<Any?, T>
<T : Any> SavedStateHandle.saved(
    key: String,
    serializer: <Error class: unknown class><T>,
    init: () -> T
)

Returns a property delegate that uses SavedStateHandle to save and restore a value of type T.

Cmn
inline T
<T : Any> SavedStateHandle.toRoute(typeMap: Map<KTypeNavType<*>>)

Returns route as an object of type T

android
T
<T : Any> SavedStateHandle.toRoute(
    route: KClass<T>,
    typeMap: Map<KTypeNavType<*>>
)

Returns route as an object of type T

android
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

Public constructors

SavedStateHandle

SavedStateHandle()

Creates a handle with the empty state.

SavedStateHandle

SavedStateHandle(initialState: Map<StringAny?>)

Creates a handle with the given initial arguments.

Parameters
initialState: Map<StringAny?>

initial arguments for the SavedStateHandle

Public functions

clearSavedStateProvider

@MainThread
fun clearSavedStateProvider(key: String): Unit

Clear any SavedStateProvider that was previously set via setSavedStateProvider.

Note: calling this method within SavedStateProvider.saveState is supported, but will only affect future state saving operations.

Parameters
key: String

a key previously used with setSavedStateProvider

contains

@MainThread
operator fun contains(key: String): Boolean
Parameters
key: String

The identifier for the value

Returns
Boolean

true if there is value associated with the given key.

get

@MainThread
operator fun <T : Any?> get(key: String): T?

Returns a value associated with the given key.

Note: If T is an Array of Parcelable classes, note that you should always use Array<Parcelable> and create a typed array from the result as going through process death and recreation (or using the Don't keep activities developer option) will result in the type information being lost, thus resulting in a ClassCastException if you directly try to assign the result to an Array<CustomParcelable> value.

val typedArray = savedStateHandle.get<Array<Parcelable>>("KEY").map {
it as CustomParcelable
}.toTypedArray()
Parameters
key: String

a key used to retrieve a value.

getMutableStateFlow

@MainThread
fun <T : Any?> getMutableStateFlow(key: String, initialValue: T): MutableStateFlow<T>

Returns a MutableStateFlow that will emit the currently active value associated with the given key.

val flow = savedStateHandle.getMutableStateFlow(KEY, "defaultValue")

Since this is a MutableStateFlow there will always be a value available which, is why an initial value must be provided. The value of this flow is changed by making a call to set, passing in the key that references this flow or by updating the value of the returned MutableStateFlow

If there is already a value associated with the given key, the initial value will be ignored.

Note 1: If T is an Array of Parcelable classes, note that you should always use Array<Parcelable> and create a typed array from the result as going through process death and recreation (or using the Don't keep activities developer option) will result in the type information being lost, thus resulting in a ClassCastException if you directly try to collect the result as an Array<CustomParcelable>.

val typedArrayFlow = savedStateHandle.getMutableStateFlow<Array<Parcelable>>(
"KEY"
).map { array ->
// Convert the Array<Parcelable> to an Array<CustomParcelable>
array.map { it as CustomParcelable }.toTypedArray()
}

Note 2: On Android, this method is mutually exclusive with getLiveData for the same key. You should use either getMutableStateFlow or getLiveData to access the stored value, but not both. Using both methods with the same key will result in an IllegalStateException.

Parameters
key: String

The identifier for the flow

initialValue: T

If no value exists with the given key, a new one is created with the given initialValue.

getStateFlow

@MainThread
fun <T : Any?> getStateFlow(key: String, initialValue: T): StateFlow<T>

Returns a StateFlow that will emit the currently active value associated with the given key.

val flow = savedStateHandle.getStateFlow(KEY, "defaultValue")

Since this is a StateFlow there will always be a value available which, is why an initial value must be provided. The value of this flow is changed by making a call to set, passing in the key that references this flow.

If there is already a value associated with the given key, the initial value will be ignored.

Note: If T is an Array of Parcelable classes, note that you should always use Array<Parcelable> and create a typed array from the result as going through process death and recreation (or using the Don't keep activities developer option) will result in the type information being lost, thus resulting in a ClassCastException if you directly try to collect the result as an Array<CustomParcelable>.

val typedArrayFlow = savedStateHandle.getStateFlow<Array<Parcelable>>(
"KEY"
).map { array ->
// Convert the Array<Parcelable> to an Array<CustomParcelable>
array.map { it as CustomParcelable }.toTypedArray()
}
Parameters
key: String

The identifier for the flow

initialValue: T

If no value exists with the given key, a new one is created with the given initialValue.

keys

@MainThread
fun keys(): Set<String>

Returns all keys contained in this SavedStateHandle

Returned set contains all keys: keys used to get LiveData-s, to set SavedStateProviders and keys used in regular set.

remove

@MainThread
fun <T : Any?> remove(key: String): T?

Removes a value associated with the given key. If there is a LiveData and/or StateFlow associated with the given key, they will be removed as well.

All changes to androidx.lifecycle.LiveDatas or StateFlows previously returned by SavedStateHandle.getLiveData or getStateFlow won't be reflected in the saved state. Also that LiveData or StateFlow won't receive any updates about new values associated by the given key.

Parameters
key: String

a key

Returns
T?

a value that was previously associated with the given key.

set

@MainThread
operator fun <T : Any?> set(key: String, value: T?): Unit

Associate the given value with the key. The value must have a type that could be stored in SavedState

This also sets values for any active LiveDatas or StateFlows.

Parameters
key: String

a key used to associate with the given value.

value: T?

object of any type that can be accepted by Bundle.

Throws
kotlin.IllegalArgumentException

value cannot be saved in saved state

setSavedStateProvider

@MainThread
fun setSavedStateProvider(
    key: String,
    provider: SavedStateRegistry.SavedStateProvider
): Unit

Set a SavedStateProvider that will have its state saved into this SavedStateHandle. This provides a mechanism to lazily provide the SavedState of saved state for the given key.

Calls to get with this same key will return the previously saved state as a SavedState if it exists.

Bundle previousState = savedStateHandle.get("custom_object");
if (previousState != null) {
// Convert the previousState into your custom object
}
savedStateHandle.setSavedStateProvider("custom_object", () -> {
Bundle savedState = new Bundle();
// Put your custom object into the Bundle, doing any conversion required
return savedState;
});

Note: calling this method within SavedStateProvider.saveState is supported, but will only affect future state saving operations.

Parameters
key: String

a key which will populated with a SavedState produced by the provider

provider: SavedStateRegistry.SavedStateProvider

a SavedStateProvider which will receive a callback to SavedStateProvider.saveState when the state should be saved

Extension functions

inline fun <T : Any> SavedStateHandle.saved(noinline init: () -> T): ReadWriteProperty<Any?, T>

Returns a property delegate that uses SavedStateHandle to save and restore a value of type T with fully qualified property or variable name as key and the default serializer.

import androidx.lifecycle.serialization.saved

@Serializable data class User(val id: Int, val name: String)
class ProfileViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
    val user by savedStateHandle.saved { User(123, "foo") }
}
Parameters
noinline init: () -> T

The function to provide the initial value of the property.

Returns
ReadWriteProperty<Any?, T>

A property delegate that manages the saving and restoring of the value.

inline fun <T : Any> SavedStateHandle.saved(key: String, noinline init: () -> T): ReadWriteProperty<Any?, T>

Returns a property delegate that uses SavedStateHandle to save and restore a value of type T with the default serializer.

import androidx.lifecycle.serialization.saved

@Serializable data class User(val id: Int, val name: String)
class ProfileViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
    val user by savedStateHandle.saved(key = "bar") { User(123, "foo") }
}
Parameters
key: String

The String key to use for storing the value in the SavedStateHandle.

noinline init: () -> T

The function to provide the initial value of the property.

Returns
ReadWriteProperty<Any?, T>

A property delegate that manages the saving and restoring of the value.

fun <T : Any> SavedStateHandle.saved(
    serializer: <Error class: unknown class><T>,
    init: () -> T
): ReadWriteProperty<Any?, T>

Returns a property delegate that uses SavedStateHandle to save and restore a value of type T with fully qualified property or variable name as key.

import androidx.lifecycle.serialization.saved

@Serializable data class User(val id: Int, val name: String)
class ProfileViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
    val user by savedStateHandle.saved(User::class.serializer()) { User(123, "foo") }
}
Parameters
serializer: <Error class: unknown class><T>

The KSerializer to use for serializing and deserializing the value.

init: () -> T

The function to provide the initial value of the property.

Returns
ReadWriteProperty<Any?, T>

A property delegate that manages the saving and restoring of the value.

fun <T : Any> SavedStateHandle.saved(
    key: String,
    serializer: <Error class: unknown class><T>,
    init: () -> T
): ReadWriteProperty<Any?, T>

Returns a property delegate that uses SavedStateHandle to save and restore a value of type T.

import androidx.lifecycle.serialization.saved

@Serializable data class User(val id: Int, val name: String)
class ProfileViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
    val user by
        savedStateHandle.saved(key = "bar", serializer = User::class.serializer()) {
            User(123, "foo")
        }
}
Parameters
key: String

The String key to use for storing the value in the SavedStateHandle.

serializer: <Error class: unknown class><T>

The KSerializer to use for serializing and deserializing the value.

init: () -> T

The function to provide the initial value of the property.

Returns
ReadWriteProperty<Any?, T>

A property delegate that manages the saving and restoring of the value.

inline fun <T : Any> SavedStateHandle.toRoute(
    typeMap: Map<KTypeNavType<*>> = emptyMap()
): T

Returns route as an object of type T

Extrapolates arguments from SavedStateHandle and recreates object T

Parameters
<T : Any>

the entry's NavDestination.route as a KClass

typeMap: Map<KTypeNavType<*>> = emptyMap()

A mapping of KType to custom NavType<*> in T. May be empty if T does not use custom NavTypes.

Returns
T

A new instance of this entry's NavDestination.route as an object of type T

fun <T : Any> SavedStateHandle.toRoute(
    route: KClass<T>,
    typeMap: Map<KTypeNavType<*>> = emptyMap()
): T

Returns route as an object of type T

Extrapolates arguments from SavedStateHandle and recreates object T

Parameters
route: KClass<T>

the entry's NavDestination.route as a KClass

typeMap: Map<KTypeNavType<*>> = emptyMap()

A mapping of KType to custom NavType<*> in T. May be empty if T does not use custom NavTypes.

Returns
T

A new instance of this entry's NavDestination.route as an object of type T

@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))
    }
}