1 /*
<lambda>null2 * 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 package androidx.compose.runtime.saveable
18
19 import androidx.compose.runtime.Composable
20 import androidx.compose.runtime.MutableState
21 import androidx.compose.runtime.RememberObserver
22 import androidx.compose.runtime.SideEffect
23 import androidx.compose.runtime.SnapshotMutationPolicy
24 import androidx.compose.runtime.currentCompositeKeyHashCode
25 import androidx.compose.runtime.mutableStateOf
26 import androidx.compose.runtime.neverEqualPolicy
27 import androidx.compose.runtime.referentialEqualityPolicy
28 import androidx.compose.runtime.remember
29 import androidx.compose.runtime.snapshots.SnapshotMutableState
30 import androidx.compose.runtime.structuralEqualityPolicy
31 import androidx.compose.runtime.toString
32
33 /**
34 * Remember the value produced by [init].
35 *
36 * It behaves similarly to [remember], but the stored value will survive the activity or process
37 * recreation using the saved instance state mechanism (for example it happens when the screen is
38 * rotated in the Android application).
39 *
40 * @sample androidx.compose.runtime.saveable.samples.RememberSaveable
41 *
42 * If you use it with types which can be stored inside the Bundle then it will be saved and restored
43 * automatically using [autoSaver], otherwise you will need to provide a custom [Saver]
44 * implementation via the [saver] param.
45 *
46 * @sample androidx.compose.runtime.saveable.samples.RememberSaveableCustomSaver
47 *
48 * You can use it with a value stored inside [androidx.compose.runtime.mutableStateOf].
49 *
50 * @sample androidx.compose.runtime.saveable.samples.RememberSaveableWithMutableState
51 *
52 * If the value inside the MutableState can be stored inside the Bundle it would be saved and
53 * restored automatically, otherwise you will need to provide a custom [Saver] implementation via an
54 * overload with which has `stateSaver` param.
55 *
56 * @sample androidx.compose.runtime.saveable.samples.RememberSaveableWithMutableStateAndCustomSaver
57 * @param inputs A set of inputs such that, when any of them have changed, will cause the state to
58 * reset and [init] to be rerun. Note that state restoration DOES NOT validate against inputs
59 * provided before value was saved.
60 * @param saver The [Saver] object which defines how the state is saved and restored.
61 * @param key An optional key to be used as a key for the saved value. If not provided we use the
62 * automatically generated by the Compose runtime which is unique for the every exact code
63 * location in the composition tree
64 * @param init A factory function to create the initial value of this state
65 */
66 @Composable
67 fun <T : Any> rememberSaveable(
68 vararg inputs: Any?,
69 saver: Saver<T, out Any> = autoSaver(),
70 key: String? = null,
71 init: () -> T
72 ): T {
73 val compositeKey = currentCompositeKeyHashCode
74 // key is the one provided by the user or the one generated by the compose runtime
75 val finalKey =
76 if (!key.isNullOrEmpty()) {
77 key
78 } else {
79 compositeKey.toString(MaxSupportedRadix)
80 }
81 @Suppress("UNCHECKED_CAST") (saver as Saver<T, Any>)
82
83 val registry = LocalSaveableStateRegistry.current
84
85 val holder = remember {
86 // value is restored using the registry or created via [init] lambda
87 val restored = registry?.consumeRestored(finalKey)?.let { saver.restore(it) }
88 val finalValue = restored ?: init()
89 SaveableHolder(saver, registry, finalKey, finalValue, inputs)
90 }
91
92 val value = holder.getValueIfInputsDidntChange(inputs) ?: init()
93 SideEffect { holder.update(saver, registry, finalKey, value, inputs) }
94
95 return value
96 }
97
98 /**
99 * Remember the value produced by [init].
100 *
101 * It behaves similarly to [remember], but the stored value will survive the activity or process
102 * recreation using the saved instance state mechanism (for example it happens when the screen is
103 * rotated in the Android application).
104 *
105 * Use this overload if you remember a mutable state with a type which can't be stored in the Bundle
106 * so you have to provide a custom saver object.
107 *
108 * @sample androidx.compose.runtime.saveable.samples.RememberSaveableWithMutableStateAndCustomSaver
109 * @param inputs A set of inputs such that, when any of them have changed, will cause the state to
110 * reset and [init] to be rerun. Note that state restoration DOES NOT validate against inputs
111 * provided before value was saved.
112 * @param stateSaver The [Saver] object which defines how the value inside the MutableState is saved
113 * and restored.
114 * @param key An optional key to be used as a key for the saved value. If not provided we use the
115 * automatically generated by the Compose runtime which is unique for the every exact code
116 * location in the composition tree
117 * @param init A factory function to create the initial value of this state
118 */
119 @Composable
rememberSaveablenull120 fun <T> rememberSaveable(
121 vararg inputs: Any?,
122 stateSaver: Saver<T, out Any>,
123 key: String? = null,
124 init: () -> MutableState<T>
125 ): MutableState<T> =
126 rememberSaveable(*inputs, saver = mutableStateSaver(stateSaver), key = key, init = init)
127
128 private class SaveableHolder<T>(
129 private var saver: Saver<T, Any>,
130 private var registry: SaveableStateRegistry?,
131 private var key: String,
132 private var value: T,
133 private var inputs: Array<out Any?>
134 ) : SaverScope, RememberObserver {
135 private var entry: SaveableStateRegistry.Entry? = null
136 /** Value provider called by the registry. */
137 private val valueProvider = {
138 with(saver) { save(requireNotNull(value) { "Value should be initialized" }) }
139 }
140
141 fun update(
142 saver: Saver<T, Any>,
143 registry: SaveableStateRegistry?,
144 key: String,
145 value: T,
146 inputs: Array<out Any?>
147 ) {
148 var entryIsOutdated = false
149 if (this.registry !== registry) {
150 this.registry = registry
151 entryIsOutdated = true
152 }
153 if (this.key != key) {
154 this.key = key
155 entryIsOutdated = true
156 }
157 this.saver = saver
158 this.value = value
159 this.inputs = inputs
160 if (entry != null && entryIsOutdated) {
161 entry?.unregister()
162 entry = null
163 register()
164 }
165 }
166
167 private fun register() {
168 val registry = registry
169 require(entry == null) { "entry($entry) is not null" }
170 if (registry != null) {
171 registry.requireCanBeSaved(valueProvider())
172 entry = registry.registerProvider(key, valueProvider)
173 }
174 }
175
176 override fun canBeSaved(value: Any): Boolean {
177 val registry = registry
178 return registry == null || registry.canBeSaved(value)
179 }
180
181 override fun onRemembered() {
182 register()
183 }
184
185 override fun onForgotten() {
186 entry?.unregister()
187 }
188
189 override fun onAbandoned() {
190 entry?.unregister()
191 }
192
193 fun getValueIfInputsDidntChange(inputs: Array<out Any?>): T? {
194 return if (inputs.contentEquals(this.inputs)) {
195 value
196 } else {
197 null
198 }
199 }
200 }
201
202 @Suppress("UNCHECKED_CAST")
mutableStateSavernull203 private fun <T> mutableStateSaver(inner: Saver<T, out Any>) =
204 with(inner as Saver<T, Any>) {
205 Saver<MutableState<T>, MutableState<Any?>>(
206 save = { state ->
207 require(state is SnapshotMutableState<T>) {
208 "If you use a custom MutableState implementation you have to write a custom " +
209 "Saver and pass it as a saver param to rememberSaveable()"
210 }
211 val saved = save(state.value)
212 if (saved != null) {
213 mutableStateOf(saved, state.policy as SnapshotMutationPolicy<Any?>)
214 } else {
215 // if the inner saver returned null we need to return null as well so the
216 // user's init lambda will be used instead of restoring mutableStateOf(null)
217 null
218 }
219 },
220 restore =
221 @Suppress("UNCHECKED_CAST", "ExceptionMessage") {
222 require(it is SnapshotMutableState<Any?>)
223 mutableStateOf(
224 if (it.value != null) restore(it.value!!) else null,
225 it.policy as SnapshotMutationPolicy<T?>
226 )
227 as MutableState<T>
228 }
229 )
230 }
231
requireCanBeSavednull232 private fun SaveableStateRegistry.requireCanBeSaved(value: Any?) {
233 if (value != null && !canBeSaved(value)) {
234 throw IllegalArgumentException(
235 if (value is SnapshotMutableState<*>) {
236 if (
237 value.policy !== neverEqualPolicy<Any?>() &&
238 value.policy !== structuralEqualityPolicy<Any?>() &&
239 value.policy !== referentialEqualityPolicy<Any?>()
240 ) {
241 "If you use a custom SnapshotMutationPolicy for your MutableState you have to" +
242 " write a custom Saver"
243 } else {
244 "MutableState containing ${value.value} cannot be saved using the current " +
245 "SaveableStateRegistry. The default implementation only supports types " +
246 "which can be stored inside the Bundle. Please consider implementing a " +
247 "custom Saver for this class and pass it as a stateSaver parameter to " +
248 "rememberSaveable()."
249 }
250 } else {
251 generateCannotBeSavedErrorMessage(value)
252 }
253 )
254 }
255 }
256
generateCannotBeSavedErrorMessagenull257 internal fun generateCannotBeSavedErrorMessage(value: Any): String =
258 "$value cannot be saved using the current SaveableStateRegistry. The default " +
259 "implementation only supports types which can be stored inside the Bundle" +
260 ". Please consider implementing a custom Saver for this class and pass it" +
261 " to rememberSaveable()."
262
263 /** The maximum radix available for conversion to and from strings. */
264 private val MaxSupportedRadix = 36
265