All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.androidx.compose.runtime.saveable.SaveableStateHolder.kt Maven / Gradle / Ivy

The newest version!
/*
 * 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.saveable

import androidx.collection.mutableScatterMapOf
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.ReusableContent
import androidx.compose.runtime.remember

/**
 * Allows to save the state defined with [rememberSaveable] for the subtree before disposing it to
 * make it possible to compose it back next time with the restored state. It allows different
 * navigation patterns to keep the ui state like scroll position for the currently not composed
 * screens from the backstack.
 *
 * @sample androidx.compose.runtime.saveable.samples.SimpleNavigationWithSaveableStateSample
 *
 * The content should be composed using [SaveableStateProvider] while providing a key representing
 * this content. Next time [SaveableStateProvider] will be used with the same key its state will be
 * restored.
 */
interface SaveableStateHolder {
    /**
     * Put your content associated with a [key] inside the [content]. This will automatically save
     * all the states defined with [rememberSaveable] before disposing the content and will restore
     * the states when you compose with this key again.
     *
     * @param key to be used for saving and restoring the states for the subtree. Note that on
     *   Android you can only use types which can be stored inside the Bundle.
     * @param content the content for which [key] is associated.
     */
    @Composable fun SaveableStateProvider(key: Any, content: @Composable () -> Unit)

    /** Removes the saved state associated with the passed [key]. */
    fun removeState(key: Any)
}

/** Creates and remembers the instance of [SaveableStateHolder]. */
@Composable
fun rememberSaveableStateHolder(): SaveableStateHolder =
    rememberSaveable(saver = SaveableStateHolderImpl.Saver) { SaveableStateHolderImpl() }
        .apply { parentSaveableStateRegistry = LocalSaveableStateRegistry.current }

private class SaveableStateHolderImpl(
    private val savedStates: MutableMap>> = mutableMapOf()
) : SaveableStateHolder {
    private val registryHolders = mutableScatterMapOf()
    var parentSaveableStateRegistry: SaveableStateRegistry? = null

    @Composable
    override fun SaveableStateProvider(key: Any, content: @Composable () -> Unit) {
        ReusableContent(key) {
            val registryHolder = remember {
                require(parentSaveableStateRegistry?.canBeSaved(key) ?: true) {
                    "Type of the key $key is not supported. On Android you can only use types " +
                        "which can be stored inside the Bundle."
                }
                RegistryHolder(key)
            }
            CompositionLocalProvider(
                LocalSaveableStateRegistry provides registryHolder.registry,
                content = content
            )
            DisposableEffect(Unit) {
                require(key !in registryHolders) { "Key $key was used multiple times " }
                savedStates -= key
                registryHolders[key] = registryHolder
                onDispose {
                    registryHolder.saveTo(savedStates)
                    registryHolders -= key
                }
            }
        }
    }

    private fun saveAll(): MutableMap>>? {
        val map = savedStates.toMutableMap()
        registryHolders.forEachValue { it.saveTo(map) }
        return map.ifEmpty { null }
    }

    override fun removeState(key: Any) {
        val registryHolder = registryHolders[key]
        if (registryHolder != null) {
            registryHolder.shouldSave = false
        } else {
            savedStates -= key
        }
    }

    inner class RegistryHolder constructor(val key: Any) {
        var shouldSave = true
        val registry: SaveableStateRegistry =
            SaveableStateRegistry(savedStates[key]) {
                parentSaveableStateRegistry?.canBeSaved(it) ?: true
            }

        fun saveTo(map: MutableMap>>) {
            if (shouldSave) {
                val savedData = registry.performSave()
                if (savedData.isEmpty()) {
                    map -= key
                } else {
                    map[key] = savedData
                }
            }
        }
    }

    companion object {
        val Saver: Saver =
            Saver(save = { it.saveAll() }, restore = { SaveableStateHolderImpl(it) })
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy