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

commonMain.com.slack.circuit.foundation.SaveableStateHolder.kt Maven / Gradle / Ivy

The newest version!
// Copyright (C) 2020 Slack Technologies, LLC
// SPDX-License-Identifier: Apache-2.0
package com.slack.circuit.foundation

import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.LocalSaveableStateRegistry
import androidx.compose.runtime.saveable.SaveableStateRegistry
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.rememberSaveable
import com.slack.circuit.foundation.internal.withCompositionLocalProvider

/**
 * This is a copy of [androidx.compose.runtime.saveable.SaveableStateHolder], tweaked so that
 * [SaveableStateProvider] returns a value.
 */
internal 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.
   */
  @Composable fun  SaveableStateProvider(key: Any, content: @Composable () -> R): R

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

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

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

  @Composable
  override fun  SaveableStateProvider(key: Any, content: @Composable () -> R): R {
    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)
    }

    val result =
      withCompositionLocalProvider(
        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
      }
    }

    return result
  }

  private fun saveAll(): MutableMap>>? {
    val map = savedStates.toMutableMap()
    registryHolders.values.forEach { 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(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