1 /*
<lambda>null2 * Copyright 2020 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.mutableScatterMapOf
20 import androidx.compose.runtime.Composable
21 import androidx.compose.runtime.CompositionLocalProvider
22 import androidx.compose.runtime.DisposableEffect
23 import androidx.compose.runtime.ReusableContent
24 import androidx.compose.runtime.remember
25
26 /**
27 * Allows to save the state defined with [rememberSaveable] for the subtree before disposing it to
28 * make it possible to compose it back next time with the restored state. It allows different
29 * navigation patterns to keep the ui state like scroll position for the currently not composed
30 * screens from the backstack.
31 *
32 * @sample androidx.compose.runtime.saveable.samples.SimpleNavigationWithSaveableStateSample
33 *
34 * The content should be composed using [SaveableStateProvider] while providing a key representing
35 * this content. Next time [SaveableStateProvider] will be used with the same key its state will be
36 * restored.
37 */
38 interface SaveableStateHolder {
39 /**
40 * Put your content associated with a [key] inside the [content]. This will automatically save
41 * all the states defined with [rememberSaveable] before disposing the content and will restore
42 * the states when you compose with this key again.
43 *
44 * @param key to be used for saving and restoring the states for the subtree. Note that on
45 * Android you can only use types which can be stored inside the Bundle.
46 * @param content the content for which [key] is associated.
47 */
48 @Composable fun SaveableStateProvider(key: Any, content: @Composable () -> Unit)
49
50 /** Removes the saved state associated with the passed [key]. */
51 fun removeState(key: Any)
52 }
53
54 /** Creates and remembers the instance of [SaveableStateHolder]. */
55 @Composable
rememberSaveableStateHoldernull56 fun rememberSaveableStateHolder(): SaveableStateHolder =
57 rememberSaveable(saver = SaveableStateHolderImpl.Saver) { SaveableStateHolderImpl() }
<lambda>null58 .apply { parentSaveableStateRegistry = LocalSaveableStateRegistry.current }
59
60 private class SaveableStateHolderImpl(
61 private val savedStates: MutableMap<Any, Map<String, List<Any?>>> = mutableMapOf()
62 ) : SaveableStateHolder {
63 private val registries = mutableScatterMapOf<Any, SaveableStateRegistry>()
64 var parentSaveableStateRegistry: SaveableStateRegistry? = null
<lambda>null65 private val canBeSaved: (Any) -> Boolean = {
66 parentSaveableStateRegistry?.canBeSaved(it) ?: true
67 }
68
69 @Composable
70 override fun SaveableStateProvider(key: Any, content: @Composable () -> Unit) {
<lambda>null71 ReusableContent(key) {
72 val registry = remember {
73 require(canBeSaved(key)) {
74 "Type of the key $key is not supported. On Android you can only use types " +
75 "which can be stored inside the Bundle."
76 }
77 SaveableStateRegistry(savedStates[key], canBeSaved)
78 }
79 CompositionLocalProvider(
80 LocalSaveableStateRegistry provides registry,
81 content = content
82 )
83 DisposableEffect(Unit) {
84 require(key !in registries) { "Key $key was used multiple times " }
85 savedStates -= key
86 registries[key] = registry
87 onDispose {
88 if (registries.remove(key) === registry) {
89 registry.saveTo(savedStates, key)
90 }
91 }
92 }
93 }
94 }
95
saveAllnull96 private fun saveAll(): MutableMap<Any, Map<String, List<Any?>>>? {
97 val map = savedStates
98 registries.forEach { key, registry -> registry.saveTo(map, key) }
99 return map.ifEmpty { null }
100 }
101
removeStatenull102 override fun removeState(key: Any) {
103 if (registries.remove(key) == null) {
104 savedStates -= key
105 }
106 }
107
SaveableStateRegistrynull108 private fun SaveableStateRegistry.saveTo(
109 map: MutableMap<Any, Map<String, List<Any?>>>,
110 key: Any
111 ) {
112 val savedData = performSave()
113 if (savedData.isEmpty()) {
114 map -= key
115 } else {
116 map[key] = savedData
117 }
118 }
119
120 companion object {
121 val Saver: Saver<SaveableStateHolderImpl, *> =
<lambda>null122 Saver(save = { it.saveAll() }, restore = { SaveableStateHolderImpl(it) })
123 }
124 }
125