|
|
|
@ -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
|
|
|
|
|
)
|
|
|
|
|