From 7f1c2512de077dcf695ea2e0c05005e35c61e51c Mon Sep 17 00:00:00 2001 From: Alex Vanyo Date: Thu, 5 Dec 2024 20:26:17 -0800 Subject: [PATCH] Serialize state to parcel for state restoration tester --- kmp-state-restoration-tester/build.gradle.kts | 7 +- .../KmpStateRestorationTester.android.kt | 92 +++++++++++++++++++ .../KmpStateRestorationTester.desktop.kt | 57 ++++++++++++ .../KmpStateRestorationTester.kt | 36 ++------ 4 files changed, 162 insertions(+), 30 deletions(-) create mode 100644 kmp-state-restoration-tester/src/androidMain/kotlin/com/alexvanyo/composelife/kmpstaterestorationtester/KmpStateRestorationTester.android.kt create mode 100644 kmp-state-restoration-tester/src/desktopMain/kotlin/com/alexvanyo/composelife/kmpstaterestorationtester/KmpStateRestorationTester.desktop.kt diff --git a/kmp-state-restoration-tester/build.gradle.kts b/kmp-state-restoration-tester/build.gradle.kts index cc65f2cd15..8506e8ef3f 100644 --- a/kmp-state-restoration-tester/build.gradle.kts +++ b/kmp-state-restoration-tester/build.gradle.kts @@ -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 { diff --git a/kmp-state-restoration-tester/src/androidMain/kotlin/com/alexvanyo/composelife/kmpstaterestorationtester/KmpStateRestorationTester.android.kt b/kmp-state-restoration-tester/src/androidMain/kotlin/com/alexvanyo/composelife/kmpstaterestorationtester/KmpStateRestorationTester.android.kt new file mode 100644 index 0000000000..0b317830d3 --- /dev/null +++ b/kmp-state-restoration-tester/src/androidMain/kotlin/com/alexvanyo/composelife/kmpstaterestorationtester/KmpStateRestorationTester.android.kt @@ -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>? { + val map = mutableMapOf>() + this.keySet().forEach { key -> + val list = getParcelableArrayList(key) as ArrayList + map[key] = list + } + return map +} + +// Copied from DisposableSaveableStateRegistry.android.kt +@Suppress("UNCHECKED_CAST") +private fun Map>.toBundle(): Bundle { + val bundle = Bundle() + forEach { (key, list) -> + val arrayList = if (list is ArrayList) list else ArrayList(list) + bundle.putParcelableArrayList( + key, + arrayList as ArrayList, + ) + } + return bundle +} diff --git a/kmp-state-restoration-tester/src/desktopMain/kotlin/com/alexvanyo/composelife/kmpstaterestorationtester/KmpStateRestorationTester.desktop.kt b/kmp-state-restoration-tester/src/desktopMain/kotlin/com/alexvanyo/composelife/kmpstaterestorationtester/KmpStateRestorationTester.desktop.kt new file mode 100644 index 0000000000..6ca7017b61 --- /dev/null +++ b/kmp-state-restoration-tester/src/desktopMain/kotlin/com/alexvanyo/composelife/kmpstaterestorationtester/KmpStateRestorationTester.desktop.kt @@ -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> = 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) diff --git a/kmp-state-restoration-tester/src/jbMain/kotlin/com/alexvanyo/composelife/kmpstaterestorationtester/KmpStateRestorationTester.kt b/kmp-state-restoration-tester/src/jbMain/kotlin/com/alexvanyo/composelife/kmpstaterestorationtester/KmpStateRestorationTester.kt index 84ee60930b..a800b4454f 100644 --- a/kmp-state-restoration-tester/src/jbMain/kotlin/com/alexvanyo/composelife/kmpstaterestorationtester/KmpStateRestorationTester.kt +++ b/kmp-state-restoration-tester/src/jbMain/kotlin/com/alexvanyo/composelife/kmpstaterestorationtester/KmpStateRestorationTester.kt @@ -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 @@ -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> = 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