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