Use androidx SavedStateHandle.saveable

Change-Id: Ifdae07c5b3731c4d67f348a01652cf3aec00b234
pull/2/head
Alex Vanyo 2 years ago committed by Don Turner
parent 2652088cf0
commit d33c2546ee

@ -16,40 +16,33 @@
package com.google.samples.apps.nowinandroid.feature.foryou.util
import android.os.Binder
import android.os.Bundle
import android.os.Parcelable
import android.util.Size
import android.util.SizeF
import android.util.SparseArray
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.SnapshotMutationPolicy
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.neverEqualPolicy
import androidx.compose.runtime.referentialEqualityPolicy
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import androidx.compose.runtime.saveable.autoSaver
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshots.SnapshotMutableState
import androidx.compose.runtime.structuralEqualityPolicy
import androidx.core.os.bundleOf
import androidx.lifecycle.SavedStateHandle
import java.io.Serializable
import androidx.lifecycle.viewmodel.compose.SavedStateHandleSaveableApi
import androidx.lifecycle.viewmodel.compose.saveable
import kotlin.properties.PropertyDelegateProvider
import kotlin.properties.ReadOnlyProperty
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
// These are placeholder solutions for https://issuetracker.google.com/issues/195689777
// These are placeholder solutions for https://issuetracker.google.com/issues/224565154 and
// https://issuetracker.google.com/issues/225014345.
// With the following, it is possible to use the Compose Saver APIs to back values in the
// SavedStateHandle to persist through process death, in a similar form as rememberSaveable
/**
* A [PropertyDelegateProvider] allowing the use of [saveable] with the key provided by the name
* of the property.
*
* https://issuetracker.google.com/issues/225014345
*/
fun <T : Any> SavedStateHandle.saveable(
saver: Saver<T, out Any> = autoSaver(),
@ -75,6 +68,9 @@ fun <T : Any> SavedStateHandle.saveable(
* ```
* val value by savedStateHandle.saveable { mutableStateOf("initialValue") }
* ```
*
* https://issuetracker.google.com/issues/224565154 and
* https://issuetracker.google.com/issues/225014345
*/
@JvmName("saveableMutableState")
fun <T : Any> SavedStateHandle.saveable(
@ -97,35 +93,6 @@ fun <T : Any> SavedStateHandle.saveable(
}
}
/**
* A basic interop between [SavedStateHandle] and [Saver], so the latter can be used to save
* state holders into the [SavedStateHandle].
*
* This implementation is based on [rememberSaveable], [SaveableStateRegistry] and
* [DisposableSaveableStateRegistry], with some simplifications since there will be exactly one
* state provider storing exactly one value.
*
* This implementation makes use of [SavedStateHandle.setSavedStateProvider], so this
* state will not be kept in sync with any other way to change the internal state
* of the [SavedStateHandle].
*/
fun <T : Any> SavedStateHandle.saveable(
key: String,
saver: Saver<T, out Any> = autoSaver(),
init: () -> T,
): T {
@Suppress("UNCHECKED_CAST")
saver as Saver<T, Any>
// value is restored using the SavedStateHandle or created via [init] lambda
val value = get<Bundle?>(key)?.get("value")?.let(saver::restore) ?: init()
// Hook up saving the state to the SavedStateHandle
setSavedStateProvider(key) {
bundleOf("value" to with(saver) { SaverScope(::canBeSavedToBundle).save(value) })
}
return value
}
/**
* A basic interop between [SavedStateHandle] and [Saver], so the latter can be used to save
* state holders into the [SavedStateHandle].
@ -140,7 +107,10 @@ fun <T : Any> SavedStateHandle.saveable(
*
* Use this overload if you remember a mutable state with a type which can't be stored in the
* Bundle so you have to provide a custom saver object.
*
* https://issuetracker.google.com/issues/224565154
*/
@OptIn(SavedStateHandleSaveableApi::class)
fun <T> SavedStateHandle.saveable(
key: String,
stateSaver: Saver<T, out Any>,
@ -173,57 +143,3 @@ private fun <T> mutableStateSaver(inner: Saver<T, out Any>) = with(inner as Save
}
)
}
/**
* Checks that [value] can be stored inside [Bundle].
*
* Copied from DisposableSaveableStateRegistry.android.kt
*/
private fun canBeSavedToBundle(value: Any): Boolean {
// SnapshotMutableStateImpl is Parcelable, but we do extra checks
if (value is SnapshotMutableState<*>) {
if (value.policy === neverEqualPolicy<Any?>() ||
value.policy === structuralEqualityPolicy<Any?>() ||
value.policy === referentialEqualityPolicy<Any?>()
) {
val stateValue = value.value
return if (stateValue == null) true else canBeSavedToBundle(stateValue)
} else {
return false
}
}
for (cl in AcceptableClasses) {
if (cl.isInstance(value)) {
return true
}
}
return false
}
/**
* Contains Classes which can be stored inside [Bundle].
*
* Some of the classes are not added separately because:
*
* This classes implement Serializable:
* - Arrays (DoubleArray, BooleanArray, IntArray, LongArray, ByteArray, FloatArray, ShortArray,
* CharArray, Array<Parcelable, Array<String>)
* - ArrayList
* - Primitives (Boolean, Int, Long, Double, Float, Byte, Short, Char) will be boxed when casted
* to Any, and all the boxed classes implements Serializable.
* This class implements Parcelable:
* - Bundle
*
* Note: it is simplified copy of the array from SavedStateHandle (lifecycle-viewmodel-savedstate).
*
* Copied from DisposableSaveableStateRegistry.android.kt
*/
private val AcceptableClasses = arrayOf(
Serializable::class.java,
Parcelable::class.java,
String::class.java,
SparseArray::class.java,
Binder::class.java,
Size::class.java,
SizeF::class.java
)

@ -11,7 +11,7 @@ androidxCore = "1.7.0"
androidxDataStore = "1.0.0"
androidxEspresso = "3.3.0"
androidxHiltNavigationCompose = "1.0.0-rc01"
androidxLifecycle = "2.4.0"
androidxLifecycle = "2.5.0-alpha05"
androidxMacroBenchmark = "1.1.0-beta04"
androidxNavigation = "2.4.0-rc01"
androidxProfileinstaller = "1.2.0-alpha02"

Loading…
Cancel
Save