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.collection.MutableScatterMap
20 import androidx.collection.mutableScatterMapOf
21 import androidx.compose.runtime.saveable.SaveableStateRegistry.Entry
22 import androidx.compose.runtime.staticCompositionLocalOf
23
24 /** Allows components to save and restore their state using the saved instance state mechanism. */
25 interface SaveableStateRegistry {
26 /**
27 * Returns the restored value for the given key. Once being restored the value is cleared, so
28 * you can't restore the same key twice.
29 *
30 * @param key Key used to save the value
31 */
32 fun consumeRestored(key: String): Any?
33
34 /**
35 * Registers the value provider.
36 *
37 * There are could be multiple providers registered for the same [key]. In this case the order
38 * in which they were registered matters.
39 *
40 * Say we registered two providers for the key. One provides "1", second provides "2".
41 * [performSave] in this case will have listOf("1", "2) as a value for the key in the map. And
42 * later, when the registry will be recreated with the previously saved values, the first
43 * execution of [consumeRestored] would consume "1" and the second one "2".
44 *
45 * @param key Key to use for storing the value
46 * @param valueProvider Provides the current value, to be executed when [performSave] will be
47 * triggered to collect all the registered values
48 * @return the registry entry which you can use to unregister the provider
49 */
50 fun registerProvider(key: String, valueProvider: () -> Any?): Entry
51
52 /**
53 * Returns true if the value can be saved using this Registry. The default implementation will
54 * return true if this value can be stored in Bundle.
55 *
56 * @param value The value which we want to save using this Registry
57 */
58 fun canBeSaved(value: Any): Boolean
59
60 /**
61 * Executes all the registered value providers and combines these values into a map. We have a
62 * list of values for each key as it is allowed to have multiple providers for the same key.
63 */
64 fun performSave(): Map<String, List<Any?>>
65
66 /** The registry entry which you get when you use [registerProvider]. */
67 interface Entry {
68 /** Unregister previously registered entry. */
69 fun unregister()
70 }
71 }
72
73 /**
74 * Creates [SaveableStateRegistry].
75 *
76 * @param restoredValues The map of the restored values
77 * @param canBeSaved Function which returns true if the given value can be saved by the registry
78 */
SaveableStateRegistrynull79 fun SaveableStateRegistry(
80 restoredValues: Map<String, List<Any?>>?,
81 canBeSaved: (Any) -> Boolean
82 ): SaveableStateRegistry = SaveableStateRegistryImpl(restoredValues, canBeSaved)
83
84 /** CompositionLocal with a current [SaveableStateRegistry] instance. */
85 val LocalSaveableStateRegistry = staticCompositionLocalOf<SaveableStateRegistry?> { null }
86
87 // CharSequence.isBlank() allocates an iterator because it calls indices.all{}
CharSequencenull88 private fun CharSequence.fastIsBlank(): Boolean {
89 var blank = true
90 for (i in 0 until length) {
91 if (!this[i].isWhitespace()) {
92 blank = false
93 break
94 }
95 }
96 return blank
97 }
98
toMutableScatterMapnull99 private fun <K, V> Map<K, V>.toMutableScatterMap(): MutableScatterMap<K, V> {
100 return MutableScatterMap<K, V>(size).also { it += this }
101 }
102
103 private class SaveableStateRegistryImpl(
104 restored: Map<String, List<Any?>>?,
105 private val canBeSaved: (Any) -> Boolean
106 ) : SaveableStateRegistry {
107
108 private val restored: MutableScatterMap<String, List<Any?>>? =
109 if (!restored.isNullOrEmpty()) restored.toMutableScatterMap() else null
110
111 private var valueProviders: MutableScatterMap<String, MutableList<() -> Any?>>? = null
112
canBeSavednull113 override fun canBeSaved(value: Any): Boolean = canBeSaved.invoke(value)
114
115 override fun consumeRestored(key: String): Any? {
116 val list = restored?.remove(key)
117 return if (!list.isNullOrEmpty()) {
118 if (list.size > 1) {
119 restored?.put(key, list.subList(1, list.size))
120 }
121 list[0]
122 } else {
123 null
124 }
125 }
126
registerProvidernull127 override fun registerProvider(key: String, valueProvider: () -> Any?): Entry {
128 require(!key.fastIsBlank()) { "Registered key is empty or blank" }
129 val valueProviders =
130 valueProviders
131 ?: mutableScatterMapOf<String, MutableList<() -> Any?>>().also {
132 valueProviders = it
133 }
134 valueProviders.getOrPut(key) { mutableListOf() }.add(valueProvider)
135 return object : Entry {
136 override fun unregister() {
137 val list = valueProviders.remove(key)
138 list?.remove(valueProvider)
139 if (!list.isNullOrEmpty()) {
140 // if there are other providers for this key return list back to the map
141 valueProviders[key] = list
142 }
143 }
144 }
145 }
146
performSavenull147 override fun performSave(): Map<String, List<Any?>> {
148 if (restored == null && valueProviders == null) {
149 return emptyMap()
150 }
151 // TODO: Use a MutableScatterMap.asMap(), but we first need to make that map wrapper
152 // serializable
153 val expectedMapSize = (restored?.size ?: 0) + (valueProviders?.size ?: 0)
154 val map =
155 HashMap<String, List<Any?>>(expectedMapSize).apply {
156 restored?.forEach { k, v -> this[k] = v }
157 }
158 valueProviders?.forEach { key, list ->
159 if (list.size == 1) {
160 val value = list[0].invoke()
161 if (value != null) {
162 check(canBeSaved(value)) { generateCannotBeSavedErrorMessage(value) }
163 map[key] = arrayListOf<Any?>(value)
164 }
165 } else {
166 // if we have multiple providers we should store null values as well to preserve
167 // the order in which providers were registered. say there were two providers.
168 // the first provider returned null(nothing to save) and the second one returned
169 // "1". when we will be restoring the first provider would restore null (it is the
170 // same as to have nothing to restore) and the second one restore "1".
171 map[key] =
172 List(list.size) { index ->
173 val value = list[index].invoke()
174 if (value != null) {
175 check(canBeSaved(value)) { generateCannotBeSavedErrorMessage(value) }
176 }
177 value
178 }
179 }
180 }
181 return map
182 }
183 }
184