|
| 1 | +package com.squareup.workflow1.android |
| 2 | + |
| 3 | +import android.os.Binder |
| 4 | +import android.os.Bundle |
| 5 | +import android.os.Parcelable |
| 6 | +import android.util.Size |
| 7 | +import android.util.SizeF |
| 8 | +import android.util.SparseArray |
| 9 | +import androidx.compose.runtime.neverEqualPolicy |
| 10 | +import androidx.compose.runtime.referentialEqualityPolicy |
| 11 | +import androidx.compose.runtime.saveable.SaveableStateRegistry |
| 12 | +import androidx.compose.runtime.snapshots.SnapshotMutableState |
| 13 | +import androidx.compose.runtime.structuralEqualityPolicy |
| 14 | +import com.squareup.workflow1.Snapshot |
| 15 | +import java.io.Serializable |
| 16 | + |
| 17 | +/** |
| 18 | + * A [SaveableStateRegistry] that can save and restore anything that can be saved in a [Bundle]. |
| 19 | + * |
| 20 | + * Similar to Compose Android's `DisposableSaveableStateRegistry`. |
| 21 | + */ |
| 22 | +internal class BundleSaveableStateRegistry private constructor( |
| 23 | + saveableStateRegistry: SaveableStateRegistry |
| 24 | +) : SaveableStateRegistry by saveableStateRegistry { |
| 25 | + constructor(restoredValues: Map<String, List<Any?>>?) : this( |
| 26 | + SaveableStateRegistry(restoredValues, ::canBeSavedToBundle) |
| 27 | + ) |
| 28 | + |
| 29 | + // TODO move the functions from SnapshotParcels.kt into runtime-android. |
| 30 | + // constructor(snapshot: Snapshot) : this(snapshot.toParcelable<Bundle>().toMap()) |
| 31 | + // |
| 32 | + // fun toSnapshot(): Snapshot = performSave().toBundle().toSnapshot() |
| 33 | +} |
| 34 | + |
| 35 | +/** |
| 36 | + * Checks that [value] can be stored inside [Bundle]. |
| 37 | + */ |
| 38 | +private fun canBeSavedToBundle(value: Any): Boolean { |
| 39 | + // SnapshotMutableStateImpl is Parcelable, but we do extra checks |
| 40 | + if (value is SnapshotMutableState<*>) { |
| 41 | + if (value.policy === neverEqualPolicy<Any?>() || |
| 42 | + value.policy === structuralEqualityPolicy<Any?>() || |
| 43 | + value.policy === referentialEqualityPolicy<Any?>() |
| 44 | + ) { |
| 45 | + val stateValue = value.value |
| 46 | + return if (stateValue == null) true else canBeSavedToBundle(stateValue) |
| 47 | + } else { |
| 48 | + return false |
| 49 | + } |
| 50 | + } |
| 51 | + // lambdas in Kotlin implement Serializable, but will crash if you really try to save them. |
| 52 | + // we check for both Function and Serializable (see kotlin.jvm.internal.Lambda) to support |
| 53 | + // custom user defined classes implementing Function interface. |
| 54 | + if (value is Function<*> && value is Serializable) { |
| 55 | + return false |
| 56 | + } |
| 57 | + for (cl in AcceptableClasses) { |
| 58 | + if (cl.isInstance(value)) { |
| 59 | + return true |
| 60 | + } |
| 61 | + } |
| 62 | + return false |
| 63 | +} |
| 64 | + |
| 65 | +/** |
| 66 | + * Contains Classes which can be stored inside [Bundle]. |
| 67 | + * |
| 68 | + * Some of the classes are not added separately because: |
| 69 | + * |
| 70 | + * - These classes implement Serializable: |
| 71 | + * - Arrays (DoubleArray, BooleanArray, IntArray, LongArray, ByteArray, FloatArray, ShortArray, |
| 72 | + * CharArray, Array<Parcelable>, Array<String>) |
| 73 | + * - ArrayList |
| 74 | + * - Primitives (Boolean, Int, Long, Double, Float, Byte, Short, Char) will be boxed when casted |
| 75 | + * to Any, and all the boxed classes implements Serializable. |
| 76 | + * - This class implements Parcelable: |
| 77 | + * - Bundle |
| 78 | + * |
| 79 | + * Note: it is simplified copy of the array from SavedStateHandle (lifecycle-viewmodel-savedstate). |
| 80 | + */ |
| 81 | +private val AcceptableClasses = arrayOf( |
| 82 | + Serializable::class.java, |
| 83 | + Parcelable::class.java, |
| 84 | + String::class.java, |
| 85 | + SparseArray::class.java, |
| 86 | + Binder::class.java, |
| 87 | + Size::class.java, |
| 88 | + SizeF::class.java |
| 89 | +) |
| 90 | + |
| 91 | +@Suppress("DEPRECATION") |
| 92 | +private fun Bundle.toMap(): Map<String, List<Any?>>? { |
| 93 | + val map = mutableMapOf<String, List<Any?>>() |
| 94 | + this.keySet().forEach { key -> |
| 95 | + @Suppress("UNCHECKED_CAST") |
| 96 | + val list = getParcelableArrayList<Parcelable?>(key) as ArrayList<Any?> |
| 97 | + map[key] = list |
| 98 | + } |
| 99 | + return map |
| 100 | +} |
| 101 | + |
| 102 | +private fun Map<String, List<Any?>>.toBundle(): Bundle { |
| 103 | + val bundle = Bundle() |
| 104 | + forEach { (key, list) -> |
| 105 | + val arrayList = if (list is ArrayList<Any?>) list else ArrayList(list) |
| 106 | + @Suppress("UNCHECKED_CAST") |
| 107 | + bundle.putParcelableArrayList(key, arrayList as ArrayList<Parcelable?>) |
| 108 | + } |
| 109 | + return bundle |
| 110 | +} |
0 commit comments