[go: nahoru, domu]

blob: f0dae9b1079611bc33811f2fc52ffce71c0d0e1f [file] [log] [blame]
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.compose.runtime.savedinstancestate
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLifecycleObserver
import androidx.compose.runtime.ExperimentalComposeApi
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.currentComposer
import androidx.compose.runtime.remember
/**
* Remember the value produced by [init].
*
* It behaves similarly to [remember], but the stored value will survive the activity or process
* recreation using the saved instance state mechanism (for example it happens when the screen is
* rotated in the Android application).
*
* @sample androidx.compose.runtime.savedinstancestate.samples.RememberSavedInstanceStateSample
*
* This function works nicely with mutable objects, when you update the state of this object
* instead of recreating it. If you work with immutable objects [savedInstanceState] can suit you
* more as it wraps the value into the [MutableState].
*
* If you use it with types which can be stored inside the Bundle then it will be saved and
* restored automatically using [autoSaver], otherwise you will need to provide a custom [Saver]
* implementation via the [saver] param.
*
* @sample androidx.compose.runtime.savedinstancestate.samples.CustomSaverSample
*
* @param inputs A set of inputs such that, when any of them have changed, will cause the state to
* reset and [init] to be rerun
* @param saver The [Saver] object which defines how the state is saved and restored.
* @param key An optional key to be used as a key for the saved value. If not provided we use the
* automatically generated by the Compose runtime which is unique for the every exact code location
* in the composition tree
* @param init A factory function to create the initial value of this state
*/
@OptIn(ExperimentalComposeApi::class)
@Composable
fun <T : Any> rememberSavedInstanceState(
vararg inputs: Any?,
saver: Saver<T, out Any> = autoSaver(),
key: String? = null,
init: () -> T
): T {
val finalKey = if (!key.isNullOrEmpty()) {
key
} else {
currentComposer.currentCompoundKeyHash.toString()
}
val registry = UiSavedStateRegistryAmbient.current
val valueProvider = remember(*inputs) { ValueProvider<T>() }
return valueProvider.updateAndReturnValue(registry, saver, finalKey, init)
}
private class ValueProvider<T : Any> : SaverScope, CompositionLifecycleObserver {
private var registry: UiSavedStateRegistry? = null
private var saver: Saver<T, out Any>? = null
private var key: String? = null
private var value: T? = null
@Suppress("UNCHECKED_CAST")
private fun saver(): Saver<T, Any> = (saver as Saver<T, Any>)
fun updateAndReturnValue(
registry: UiSavedStateRegistry?,
saver: Saver<T, out Any>,
key: String,
init: () -> T
): T {
val oldRegistry = this.registry
val oldKey = this.key
this.saver = saver
this.registry = registry
this.key = key
val value = value ?: run {
// TODO not restore when the input values changed (use hashKeys?) b/152014032
val restored = registry?.consumeRestored(key)?.let {
saver().restore(it)
}
val result = restored ?: init()
this.value = result
result
}
if (oldRegistry !== registry || oldKey != key) {
oldRegistry?.unregisterProvider(oldKey!!)
registry?.registerProvider(key) {
val result = with(saver()) { save(value) }
if (result != null) {
check(registry.canBeSaved(result))
}
result
}
}
return value
}
override fun canBeSaved(value: Any) = registry!!.canBeSaved(value)
override fun onLeave() {
registry?.unregisterProvider(key!!)
}
override fun onEnter() {
// no-op
}
}