Skip to content

Commit

Permalink
Serialize state to parcel for state restoration tester
Browse files Browse the repository at this point in the history
  • Loading branch information
alexvanyo committed Dec 17, 2024
1 parent fecc2fb commit 7f1c251
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 30 deletions.
7 changes: 6 additions & 1 deletion kmp-state-restoration-tester/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,12 @@ kotlin {
jvm("desktop")

sourceSets {
val commonMain by getting {}
val commonMain by getting {
dependencies {
implementation(libs.jetbrains.compose.runtime)
implementation(libs.jetbrains.compose.runtime.saveable)
}
}
val jbMain by creating {
dependsOn(commonMain)
dependencies {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright 2024 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
*
* https://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 com.alexvanyo.composelife.kmpstaterestorationtester

import android.os.Bundle
import android.os.Parcel
import android.os.Parcelable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.SaveableStateRegistry
import androidx.compose.runtime.setValue

private class RestorationRegistryImpl(private val original: SaveableStateRegistry) :
RestorationRegistry {

override var shouldEmitChildren by mutableStateOf(true)
private set
private var currentRegistry: SaveableStateRegistry = original
private lateinit var savedParcel: Parcel

override fun saveStateAndDisposeChildren() {
savedParcel = Parcel.obtain().also {
currentRegistry.performSave().toBundle().writeToParcel(it, Parcelable.PARCELABLE_WRITE_RETURN_VALUE)
}
shouldEmitChildren = false
}

override fun emitChildrenWithRestoredState() {
savedParcel.setDataPosition(0)
val restoredValues = Bundle.CREATOR.createFromParcel(savedParcel).apply {
classLoader = this@RestorationRegistryImpl.javaClass.classLoader
}.toMap()
savedParcel.recycle()
currentRegistry = SaveableStateRegistry(
restoredValues = restoredValues,
canBeSaved = { original.canBeSaved(it) },
)
shouldEmitChildren = true
}

override fun consumeRestored(key: String) = currentRegistry.consumeRestored(key)

override fun registerProvider(key: String, valueProvider: () -> Any?) =
currentRegistry.registerProvider(key, valueProvider)

override fun canBeSaved(value: Any) = currentRegistry.canBeSaved(value)

override fun performSave() = currentRegistry.performSave()
}

internal actual fun RestorationRegistry(
original: SaveableStateRegistry,
): RestorationRegistry = RestorationRegistryImpl(original)

// Copied from DisposableSaveableStateRegistry.android.kt
@Suppress("DEPRECATION", "UNCHECKED_CAST")
private fun Bundle.toMap(): Map<String, List<Any?>>? {
val map = mutableMapOf<String, List<Any?>>()
this.keySet().forEach { key ->
val list = getParcelableArrayList<Parcelable?>(key) as ArrayList<Any?>
map[key] = list
}
return map
}

// Copied from DisposableSaveableStateRegistry.android.kt
@Suppress("UNCHECKED_CAST")
private fun Map<String, List<Any?>>.toBundle(): Bundle {
val bundle = Bundle()
forEach { (key, list) ->
val arrayList = if (list is ArrayList<Any?>) list else ArrayList(list)
bundle.putParcelableArrayList(
key,
arrayList as ArrayList<Parcelable?>,
)
}
return bundle
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright 2024 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
*
* https://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 com.alexvanyo.composelife.kmpstaterestorationtester

import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.SaveableStateRegistry
import androidx.compose.runtime.setValue

private class RestorationRegistryImpl(private val original: SaveableStateRegistry) :
RestorationRegistry {

override var shouldEmitChildren by mutableStateOf(true)
private set
private var currentRegistry: SaveableStateRegistry = original
private var savedMap: Map<String, List<Any?>> = emptyMap()

override fun saveStateAndDisposeChildren() {
savedMap = currentRegistry.performSave()
shouldEmitChildren = false
}

override fun emitChildrenWithRestoredState() {
currentRegistry = SaveableStateRegistry(
restoredValues = savedMap,
canBeSaved = { original.canBeSaved(it) },
)
shouldEmitChildren = true
}

override fun consumeRestored(key: String) = currentRegistry.consumeRestored(key)

override fun registerProvider(key: String, valueProvider: () -> Any?) =
currentRegistry.registerProvider(key, valueProvider)

override fun canBeSaved(value: Any) = currentRegistry.canBeSaved(value)

override fun performSave() = currentRegistry.performSave()
}

internal actual fun RestorationRegistry(
original: SaveableStateRegistry,
): RestorationRegistry = RestorationRegistryImpl(original)
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package com.alexvanyo.composelife.kmpstaterestorationtester
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.LocalSaveableStateRegistry
import androidx.compose.runtime.saveable.SaveableStateRegistry
Expand Down Expand Up @@ -95,35 +94,14 @@ class KmpStateRestorationTester(private val composeUiTest: ComposeUiTest) {
}
}
}
}

private class RestorationRegistry(private val original: SaveableStateRegistry) :
SaveableStateRegistry {

var shouldEmitChildren by mutableStateOf(true)
private set
private var currentRegistry: SaveableStateRegistry = original
private var savedMap: Map<String, List<Any?>> = emptyMap()

fun saveStateAndDisposeChildren() {
savedMap = currentRegistry.performSave()
shouldEmitChildren = false
}

fun emitChildrenWithRestoredState() {
currentRegistry = SaveableStateRegistry(
restoredValues = savedMap,
canBeSaved = { original.canBeSaved(it) },
)
shouldEmitChildren = true
}

override fun consumeRestored(key: String) = currentRegistry.consumeRestored(key)

override fun registerProvider(key: String, valueProvider: () -> Any?) =
currentRegistry.registerProvider(key, valueProvider)
internal interface RestorationRegistry : SaveableStateRegistry {
val shouldEmitChildren: Boolean

override fun canBeSaved(value: Any) = currentRegistry.canBeSaved(value)
fun saveStateAndDisposeChildren()

override fun performSave() = currentRegistry.performSave()
}
fun emitChildrenWithRestoredState()
}

internal expect fun RestorationRegistry(original: SaveableStateRegistry): RestorationRegistry

0 comments on commit 7f1c251

Please sign in to comment.