blob: c05db3c81e2c0a8a54894b459fabd55187c9e179 [file]
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:JvmName("SnapshotStateKt")
@file:JvmMultifileClass
package androidx.compose.runtime
import androidx.compose.runtime.snapshots.GlobalSnapshot
import androidx.compose.runtime.snapshots.Snapshot
import androidx.compose.runtime.snapshots.SnapshotId
import androidx.compose.runtime.snapshots.SnapshotMutableState
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.runtime.snapshots.SnapshotStateMap
import androidx.compose.runtime.snapshots.SnapshotStateSet
import androidx.compose.runtime.snapshots.StateFactoryMarker
import androidx.compose.runtime.snapshots.StateObjectImpl
import androidx.compose.runtime.snapshots.StateRecord
import androidx.compose.runtime.snapshots.currentSnapshot
import androidx.compose.runtime.snapshots.overwritable
import androidx.compose.runtime.snapshots.readable
import androidx.compose.runtime.snapshots.toSnapshotId
import androidx.compose.runtime.snapshots.withCurrent
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName
import kotlin.reflect.KProperty
/**
* Return a new [MutableState] initialized with the passed in [value]
*
* The MutableState class is a single value holder whose reads and writes are observed by Compose.
* Additionally, writes to it are transacted as part of the [Snapshot] system.
*
* @param value the initial value for the [MutableState]
* @param policy a policy to controls how changes are handled in mutable snapshots.
* @sample androidx.compose.runtime.samples.SimpleStateSample
* @sample androidx.compose.runtime.samples.DestructuredStateSample
* @sample androidx.compose.runtime.samples.observeUserSample
* @sample androidx.compose.runtime.samples.stateSample
* @see State
* @see MutableState
* @see SnapshotMutationPolicy
* @see mutableIntStateOf
* @see mutableLongStateOf
* @see mutableFloatStateOf
* @see mutableDoubleStateOf
*/
@StateFactoryMarker
public fun <T> mutableStateOf(
value: T,
policy: SnapshotMutationPolicy<T> = structuralEqualityPolicy(),
): MutableState<T> = createSnapshotMutableState(value, policy)
/**
* A value holder where reads to the [value] property during the execution of a [Composable]
* function, the current [RecomposeScope] will be subscribed to changes of that value.
*
* @see [MutableState]
* @see [mutableStateOf]
*/
@Stable
public interface State<out T> {
public val value: T
}
/**
* Permits property delegation of `val`s using `by` for [State].
*
* @sample androidx.compose.runtime.samples.DelegatedReadOnlyStateSample
*/
@Suppress("NOTHING_TO_INLINE")
public inline operator fun <T> State<T>.getValue(thisObj: Any?, property: KProperty<*>): T = value
/**
* A mutable value holder where reads to the [value] property during the execution of a [Composable]
* function, the current [RecomposeScope] will be subscribed to changes of that value. When the
* [value] property is written to and changed, a recomposition of any subscribed [RecomposeScope]s
* will be scheduled. If [value] is written to with the same value, no recompositions will be
* scheduled.
*
* @see [State]
* @see [mutableStateOf]
*/
@Stable
public interface MutableState<T> : State<T> {
override var value: T
public operator fun component1(): T
public operator fun component2(): (T) -> Unit
}
/**
* Permits property delegation of `var`s using `by` for [MutableState].
*
* @sample androidx.compose.runtime.samples.DelegatedStateSample
*/
@Suppress("NOTHING_TO_INLINE")
public inline operator fun <T> MutableState<T>.setValue(
thisObj: Any?,
property: KProperty<*>,
value: T,
) {
this.value = value
}
/** Returns platform specific implementation based on [SnapshotMutableStateImpl]. */
internal expect fun <T> createSnapshotMutableState(
value: T,
policy: SnapshotMutationPolicy<T>,
): SnapshotMutableState<T>
/**
* A single value holder whose reads and writes are observed by Compose.
*
* Additionally, writes to it are transacted as part of the [Snapshot] system.
*
* @param value the wrapped value
* @param policy a policy to control how changes are handled in a mutable snapshot.
* @see mutableStateOf
* @see SnapshotMutationPolicy
*/
internal open class SnapshotMutableStateImpl<T>(
value: T,
override val policy: SnapshotMutationPolicy<T>,
) : StateObjectImpl(), SnapshotMutableState<T> {
@Suppress("UNCHECKED_CAST")
override var value: T
get() = next.readable(this).value
set(value) =
next.withCurrent(this) {
if (!policy.equivalent(it.value, value)) {
next.overwritable(this, it) { this.value = value }
}
}
private var next: StateStateRecord<T> =
currentSnapshot().let { snapshot ->
StateStateRecord(snapshot.snapshotId, value).also {
if (snapshot !is GlobalSnapshot) {
it.next = StateStateRecord(Snapshot.PreexistingSnapshotId.toSnapshotId(), value)
}
}
}
override val firstStateRecord: StateRecord
get() = next
override fun prependStateRecord(value: StateRecord) {
@Suppress("UNCHECKED_CAST")
next = value as StateStateRecord<T>
}
@Suppress("UNCHECKED_CAST")
override fun mergeRecords(
previous: StateRecord,
current: StateRecord,
applied: StateRecord,
): StateRecord? {
val previousRecord = previous as StateStateRecord<T>
val currentRecord = current as StateStateRecord<T>
val appliedRecord = applied as StateStateRecord<T>
return if (policy.equivalent(currentRecord.value, appliedRecord.value)) current
else {
val merged =
policy.merge(previousRecord.value, currentRecord.value, appliedRecord.value)
if (merged != null) {
appliedRecord.create(appliedRecord.snapshotId).also { it.value = merged }
} else {
null
}
}
}
override fun toString(): String =
next.withCurrent(this) { "MutableState(value=${it.value})@${hashCode()}" }
private class StateStateRecord<T>(snapshotId: SnapshotId, myValue: T) :
StateRecord(snapshotId) {
override fun assign(value: StateRecord) {
@Suppress("UNCHECKED_CAST")
this.value = (value as StateStateRecord<T>).value
}
override fun create() = StateStateRecord(currentSnapshot().snapshotId, value)
override fun create(snapshotId: SnapshotId) = StateStateRecord(snapshotId, value)
var value: T = myValue
}
/**
* The componentN() operators allow state objects to be used with the property destructuring
* syntax
*
* ```
* var (foo, setFoo) = remember { mutableStateOf(0) }
* setFoo(123) // set
* foo == 123 // get
* ```
*/
override operator fun component1(): T = value
override operator fun component2(): (T) -> Unit = { value = it }
/**
* A function used by the debugger to display the value of the current value of the mutable
* state object without triggering read observers.
*/
@Suppress("unused")
val debuggerDisplayValue: T
@JvmName("getDebuggerDisplayValue") get() = next.withCurrent(this) { it }.value
}
/**
* Create a instance of [MutableList]<T> that is observable and can be snapshot.
*
* @sample androidx.compose.runtime.samples.stateListSample
* @see mutableStateOf
* @see mutableListOf
* @see MutableList
* @see Snapshot.takeSnapshot
*/
@StateFactoryMarker
public fun <T> mutableStateListOf(): SnapshotStateList<T> = SnapshotStateList<T>()
/**
* Create an instance of [MutableList]<T> that is observable and can be snapshot.
*
* @see mutableStateOf
* @see mutableListOf
* @see MutableList
* @see Snapshot.takeSnapshot
*/
@StateFactoryMarker
public fun <T> mutableStateListOf(vararg elements: T): SnapshotStateList<T> =
SnapshotStateList<T>().also { it.addAll(elements.toList()) }
/**
* Create an instance of [MutableList]<T> from a collection that is observable and can be snapshot.
*/
public fun <T> Collection<T>.toMutableStateList(): SnapshotStateList<T> =
SnapshotStateList<T>().also { it.addAll(this) }
/**
* Create a instance of [MutableMap]<K, V> that is observable and can be snapshot.
*
* @sample androidx.compose.runtime.samples.stateMapSample
* @see mutableStateOf
* @see mutableMapOf
* @see MutableMap
* @see Snapshot.takeSnapshot
*/
@StateFactoryMarker
public fun <K, V> mutableStateMapOf(): SnapshotStateMap<K, V> = SnapshotStateMap<K, V>()
/**
* Create a instance of [MutableMap]<K, V> that is observable and can be snapshot.
*
* @see mutableStateOf
* @see mutableMapOf
* @see MutableMap
* @see Snapshot.takeSnapshot
*/
@StateFactoryMarker
public fun <K, V> mutableStateMapOf(vararg pairs: Pair<K, V>): SnapshotStateMap<K, V> =
SnapshotStateMap<K, V>().apply { putAll(pairs.toMap()) }
/**
* Create an instance of [MutableMap]<K, V> from a collection of pairs that is observable and can be
* snapshot.
*/
@Suppress("unused")
public fun <K, V> Iterable<Pair<K, V>>.toMutableStateMap(): SnapshotStateMap<K, V> =
SnapshotStateMap<K, V>().also { it.putAll(this.toMap()) }
/**
* Create a instance of [MutableSet]<T> that is observable and can be snapshot.
*
* The returned set iteration order is in the order the items were inserted into the set.
*
* @sample androidx.compose.runtime.samples.stateSetSample
* @see mutableStateOf
* @see mutableSetOf
* @see MutableSet
* @see Snapshot.takeSnapshot
*/
@StateFactoryMarker public fun <T> mutableStateSetOf(): SnapshotStateSet<T> = SnapshotStateSet<T>()
/**
* Create an instance of [MutableSet]<T> that is observable and can be snapshot.
*
* The returned set iteration order is in the order the items were inserted into the set.
*
* @see mutableStateOf
* @see mutableSetOf
* @see MutableSet
* @see Snapshot.takeSnapshot
*/
@StateFactoryMarker
public fun <T> mutableStateSetOf(vararg elements: T): SnapshotStateSet<T> =
SnapshotStateSet<T>().also { it.addAll(elements.toSet()) }
/**
* [remember] a [mutableStateOf] [newValue] and update its value to [newValue] on each recomposition
* of the [rememberUpdatedState] call.
*
* [rememberUpdatedState] should be used when parameters or values computed during composition are
* referenced by a long-lived lambda or object expression. Recomposition will update the resulting
* [State] without recreating the long-lived lambda or object, allowing that object to persist
* without cancelling and resubscribing, or relaunching a long-lived operation that may be expensive
* or prohibitive to recreate and restart. This may be common when working with [DisposableEffect]
* or [LaunchedEffect], for example:
*
* @sample androidx.compose.runtime.samples.rememberUpdatedStateSampleWithDisposableEffect
*
* [LaunchedEffect]s often describe state machines that should not be reset and restarted if a
* parameter or event callback changes, but they should have the current value available when
* needed. For example:
*
* @sample androidx.compose.runtime.samples.rememberUpdatedStateSampleWithLaunchedEffect
*
* By using [rememberUpdatedState] a composable function can update these operations in progress.
*/
@Composable
public fun <T> rememberUpdatedState(newValue: T): State<T> =
remember { mutableStateOf(newValue) }.apply { value = newValue }