[go: nahoru, domu]

blob: e52d1438638c4849ddb601410f0b6b50087bc5e5 [file] [log] [blame]
/*
* 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.
*/
package androidx.compose
import androidx.compose.frames.AbstractRecord
import androidx.compose.frames.Framed
import androidx.compose.frames.Record
import androidx.compose.frames._created
import androidx.compose.frames.readable
import androidx.compose.frames.withCurrent
import androidx.compose.frames.writable
import kotlin.reflect.KProperty
/**
* A composable used to introduce a state value of type [T] into a composition.
*
* This is useful when you have a value that you would like to locally mutate and use in the context of a composition. Since
* the returned [MutableState] instance implements [Model], changes to the [MutableState.value] property will be automatically tracked in
* composition and schedule a recompose.
*
* The [MutableState] class can be used several different ways. For example, the most basic way is to store the returned state
* value into a local immutable variable, and then set the [MutableState.value] property on it.
*
* @sample androidx.compose.samples.SimpleStateSample
*
* Additionally, you can destructure the [MutableState] object into a value and a "setter" function.
*
* @sample androidx.compose.samples.DestructuredStateSample
*
* Finally, the [MutableState] instance can be used as a variable delegate to a local mutable variable.
*
* @sample androidx.compose.samples.DelegatedStateSample
*
* @param areEquivalent a callback to compare the previous and new instance of [T] when
* [MutableState.value] is written to. If this returns true, then no recomposition will be
* scheduled. See [ReferentiallyEqual] and [StructurallyEqual] for simple implementations.
* @param init A factory function to create the initial value of this state
* @return An [Model] instance of [MutableState] that wraps the value.
*
* @see [stateFor]
* @see [remember]
*/
@Composable
inline fun <T> state(
noinline areEquivalent: (old: T, new: T) -> Boolean = ReferentiallyEqual,
init: () -> T
) = remember { mutableStateOf(init(), areEquivalent) }
/**
* An effect to introduce a state value of type [T] into a composition that will last as long as the input [v1] does not change.
*
* This is useful when you have a value that you would like to locally mutate and use in the context of a composition, and its
* value is scoped to another value and you want it to be reset every time the other value changes.
*
* The returned [MutableState] instance implements [Model] so that changes to the [MutableState.value] property will be automatically tracked in
* composition and schedule a recompose.
*
* @param v1 An input value that, when changed, will cause the state to reset and [init] to be rerun
* @param init A factory function to create the initial value of this state
* @return An [Model] instance of [MutableState] that wraps the value.
*
* @see [state]
* @see [remember]
*/
@Composable
inline fun <T, /*reified*/ V1> stateFor(v1: V1, init: () -> T) =
remember(v1) { mutableStateOf(init()) }
/**
* An effect to introduce a state value of type [T] into a composition that will last as long as the inputs [v1] and [v2] do not change.
*
* This is useful when you have a value that you would like to locally mutate and use in the context of a composition, and its
* value is scoped to another value and you want it to be reset every time the other value changes.
*
* The returned [MutableState] instance implements [Model] so that changes to the [MutableState.value] property will be automatically tracked in
* composition and schedule a recompose.
*
* @param v1 An input value that, when changed, will cause the state to reset and [init] to be rerun
* @param v2 An input value that, when changed, will cause the state to reset and [init] to be rerun
* @param init A factory function to create the initial value of this state
* @return An [Model] instance of [MutableState] that wraps the value.
*
* @see [state]
* @see [memo]
*/
@Composable
inline fun <T, reified V1, reified V2> stateFor(
v1: V1,
v2: V2,
init: () -> T
) = remember(v1, v2) { mutableStateOf(init()) }
/**
* An effect to introduce a state value of type [T] into a composition that will last as long as the inputs [inputs] do not change.
*
* This is useful when you have a value that you would like to locally mutate and use in the context of a composition, and its
* value is scoped to another value and you want it to be reset every time the other value changes.
*
* The returned [MutableState] instance implements [Model] so that changes to the [MutableState.value] property will be automatically tracked in
* composition and schedule a recompose.
*
* @param inputs A set of inputs such that, when any of them have changed, will cause the state to reset and [init] to be rerun
* @param init A factory function to create the initial value of this state
* @return An [Model] instance of [MutableState] that wraps the value.
*
* @see [state]
* @see [remember]
*/
@Composable
inline fun <T> stateFor(vararg inputs: Any?, init: () -> T) =
remember(*inputs) { mutableStateOf(init()) }
/**
* Return a new [MutableState] initialized with the passed in [value]
*
* @param value the initial value for the [MutableState]
* @param areEquivalent a callback to compare the previous and new instance of [value] when
* it is written to. If this returns true, then no recomposition will be scheduled. See
* [ReferentiallyEqual] and [StructurallyEqual] for simple implementations.
*/
fun <T> mutableStateOf(
value: T,
areEquivalent: (old: T, new: T) -> Boolean = ReferentiallyEqual
): MutableState<T> = ModelMutableState(value, areEquivalent)
/**
* Simple comparison callback using referential `===` equality
*/
val ReferentiallyEqual = fun(old: Any?, new: Any?) = old === new
/**
* Simple comparison callback using structural [Any.equals] equality
*/
val StructurallyEqual = fun(old: Any?, new: Any?) = old == new
/**
* 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 [state]
* @see [MutableState]
* @see [mutableStateOf]
*/
@Stable
interface State<T> {
val value: T
}
/**
* 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 [State]
* @see [mutableStateOf]
*/
@Stable
interface MutableState<T> : State<T> {
override var value: T
operator fun component1(): T
operator fun component2(): (T) -> Unit
operator fun getValue(thisObj: Any?, property: KProperty<*>): T
operator fun setValue(thisObj: Any?, property: KProperty<*>, next: T)
}
/**
* The State class is an @Model class meant to wrap around a single value. It is used in the
* `state` and `stateFor` composables.
*
* @property value the wrapped value
* @property areEquivalent function used for comparing old and new [value]s to determine whether
* to trigger a recomposition or not.
*
* @see [Model]
* @see [state]
* @see [stateFor]
*/
private class ModelMutableState<T>(
value: T,
val areEquivalent: (old: T, new: T) -> Boolean
) : Framed, MutableState<T> {
/* NOTE(lmr): When this module is compiled with IR, we will need to remove the below Framed implementation */
@Suppress("UNCHECKED_CAST")
override var value: T
get() = next.readable(this).value
set(value) = next.withCurrent {
// This intentionally deviates from the typical behavior of @Model. When this is
// converted to an IR module, this behavior should be preserved with a custom setter
// on the property in the @Model class.
if (!areEquivalent(it.value, value)) {
next.writable(this).value = value
}
}
private var next: StateRecord<T> = StateRecord(value)
init {
_created(this)
}
override val firstFrameRecord: Record
get() = next
override fun prependFrameRecord(value: Record) {
value.next = next
@Suppress("UNCHECKED_CAST")
next = value as StateRecord<T>
}
private class StateRecord<T>(myValue: T) : AbstractRecord() {
override fun assign(value: Record) {
@Suppress("UNCHECKED_CAST")
this.value = (value as StateRecord<T>).value
}
override fun create(): Record =
StateRecord(value)
var value: T = myValue
}
/**
* The componentN() operators allow state objects to be used with the property destructuring syntax
*
* var (foo, setFoo) = state { 0 }
* setFoo(123) // set
* foo == 123 // get
*/
override operator fun component1(): T = value
override operator fun component2(): (T) -> Unit = { value = it }
/**
* The getValue/setValue operators allow State to be used as a local variable with a delegate:
*
* var foo by state { 0 }
* foo += 123 // uses setValue(...)
* foo == 123 // uses getValue(...)
*/
override operator fun getValue(thisObj: Any?, property: KProperty<*>): T = value
override operator fun setValue(thisObj: Any?, property: KProperty<*>, next: T) {
value = next
}
}