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