1 /*
2 * Copyright 2021 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 @file:JvmName("SnapshotStateKt")
18 @file:JvmMultifileClass
19
20 package androidx.compose.runtime
21
22 import androidx.compose.runtime.internal.JvmDefaultWithCompatibility
23 import androidx.compose.runtime.snapshots.MutableSnapshot
24 import kotlin.jvm.JvmMultifileClass
25 import kotlin.jvm.JvmName
26
27 /**
28 * A policy to control how the result of [mutableStateOf] report and merge changes to the state
29 * object.
30 *
31 * A mutation policy can be passed as an parameter to [mutableStateOf], and [compositionLocalOf].
32 *
33 * Typically, one of the stock policies should be used such as [referentialEqualityPolicy],
34 * [structuralEqualityPolicy], or [neverEqualPolicy]. However, a custom mutation policy can be
35 * created by implementing this interface, such as a counter policy,
36 *
37 * @sample androidx.compose.runtime.samples.counterSample
38 */
39 @JvmDefaultWithCompatibility
40 interface SnapshotMutationPolicy<T> {
41 /**
42 * Determine if setting a state value's are equivalent and should be treated as equal. If
43 * [equivalent] returns `true` the new value is not considered a change.
44 */
equivalentnull45 fun equivalent(a: T, b: T): Boolean
46
47 /**
48 * Merge conflicting changes in snapshots. This is only called if [current] and [applied] are
49 * not [equivalent]. If a valid merged value can be calculated then it should be returned.
50 *
51 * For example, if the state object holds an immutable data class with multiple fields, and
52 * [applied] has changed fields that are unmodified by [current] it might be valid to return a
53 * new copy of the data class that combines that changes from both [current] and [applied]
54 * allowing a snapshot to apply that would have otherwise failed.
55 *
56 * @sample androidx.compose.runtime.samples.counterSample
57 */
58 fun merge(previous: T, current: T, applied: T): T? = null
59 }
60
61 /**
62 * A policy to treat values of a [MutableState] as equivalent if they are referentially (===) equal.
63 *
64 * Setting [MutableState.value] to its current referentially (===) equal value is not considered a
65 * change. When applying a [MutableSnapshot], if the snapshot changes the value to the equivalent
66 * value the parent snapshot has is not considered a conflict.
67 */
68 @Suppress("UNCHECKED_CAST")
69 fun <T> referentialEqualityPolicy(): SnapshotMutationPolicy<T> =
70 ReferentialEqualityPolicy as SnapshotMutationPolicy<T>
71
72 private object ReferentialEqualityPolicy : SnapshotMutationPolicy<Any?> {
73 override fun equivalent(a: Any?, b: Any?) = a === b
74
75 override fun toString() = "ReferentialEqualityPolicy"
76 }
77
78 /**
79 * A policy to treat values of a [MutableState] as equivalent if they are structurally (==) equal.
80 *
81 * Setting [MutableState.value] to its current structurally (==) equal value is not considered a
82 * change. When applying a [MutableSnapshot], if the snapshot changes the value to the equivalent
83 * value the parent snapshot has is not considered a conflict.
84 */
85 @Suppress("UNCHECKED_CAST")
structuralEqualityPolicynull86 fun <T> structuralEqualityPolicy(): SnapshotMutationPolicy<T> =
87 StructuralEqualityPolicy as SnapshotMutationPolicy<T>
88
89 private object StructuralEqualityPolicy : SnapshotMutationPolicy<Any?> {
90 override fun equivalent(a: Any?, b: Any?) = a == b
91
92 override fun toString() = "StructuralEqualityPolicy"
93 }
94
95 /**
96 * A policy never treat values of a [MutableState] as equivalent.
97 *
98 * Setting [MutableState.value] will always be considered a change. When applying a
99 * [MutableSnapshot] that changes the state will always conflict with other snapshots that change
100 * the same state.
101 */
102 @Suppress("UNCHECKED_CAST")
neverEqualPolicynull103 fun <T> neverEqualPolicy(): SnapshotMutationPolicy<T> =
104 NeverEqualPolicy as SnapshotMutationPolicy<T>
105
106 private object NeverEqualPolicy : SnapshotMutationPolicy<Any?> {
107 override fun equivalent(a: Any?, b: Any?) = false
108
109 override fun toString() = "NeverEqualPolicy"
110 }
111