From 859c946aec38617937932a3b4a0e8e8a2e8d5fc9 Mon Sep 17 00:00:00 2001 From: Jingwei Hao Date: Fri, 22 Sep 2023 14:41:34 -0700 Subject: [PATCH] Add support to save non-mutable int in StateSnapshot --- redwood-lazylayout-compose/build.gradle | 1 + .../lazylayout/compose/LazyListState.kt | 18 ++++++++-- .../redwood/treehouse/treehouseCompose.kt | 1 + .../cash/redwood/treehouse/StateSnapshot.kt | 36 ++++++++++++------- .../emojisearch/presenter/EmojiSearch.kt | 14 ++++++-- 5 files changed, 54 insertions(+), 16 deletions(-) diff --git a/redwood-lazylayout-compose/build.gradle b/redwood-lazylayout-compose/build.gradle index 0ed6f7fbf5..c387c3bb72 100644 --- a/redwood-lazylayout-compose/build.gradle +++ b/redwood-lazylayout-compose/build.gradle @@ -14,6 +14,7 @@ kotlin { commonMain { dependencies { api projects.redwoodLazylayoutWidget + implementation libs.jetbrains.compose.runtime.saveable } } commonTest { diff --git a/redwood-lazylayout-compose/src/commonMain/kotlin/app/cash/redwood/lazylayout/compose/LazyListState.kt b/redwood-lazylayout-compose/src/commonMain/kotlin/app/cash/redwood/lazylayout/compose/LazyListState.kt index 5975f27c58..0225e9dbef 100644 --- a/redwood-lazylayout-compose/src/commonMain/kotlin/app/cash/redwood/lazylayout/compose/LazyListState.kt +++ b/redwood-lazylayout-compose/src/commonMain/kotlin/app/cash/redwood/lazylayout/compose/LazyListState.kt @@ -15,17 +15,18 @@ */ package app.cash.redwood.lazylayout.compose +import androidx.compose.runtime.saveable.Saver +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @Composable public fun rememberLazyListState( initialFirstVisibleItemIndex: Int = 0, ): LazyListState { - return remember { + return rememberSaveable(saver = LazyListState.Saver) { LazyListState( initialFirstVisibleItemIndex, ) @@ -46,4 +47,17 @@ public class LazyListState( firstVisibleItemIndex = index scrollToItemTriggeredId++ } + public companion object { + /** + * The default [Saver] implementation for [LazyListState]. + */ + public val Saver: Saver = Saver( + save = { it.firstVisibleItemIndex }, + restore = { + LazyListState( + firstVisibleItemIndex = it, + ) + } + ) + } } diff --git a/redwood-treehouse-guest/src/commonMain/kotlin/app/cash/redwood/treehouse/treehouseCompose.kt b/redwood-treehouse-guest/src/commonMain/kotlin/app/cash/redwood/treehouse/treehouseCompose.kt index 0e9ec91a45..9d4ffda802 100644 --- a/redwood-treehouse-guest/src/commonMain/kotlin/app/cash/redwood/treehouse/treehouseCompose.kt +++ b/redwood-treehouse-guest/src/commonMain/kotlin/app/cash/redwood/treehouse/treehouseCompose.kt @@ -110,6 +110,7 @@ private class RedwoodZiplineTreehouseUi( } override fun snapshotState(): StateSnapshot? { + // performSave is not picking up other values, why? val savedState = saveableStateRegistry.performSave() return savedState.toStateSnapshot() } diff --git a/redwood-treehouse/src/commonMain/kotlin/app/cash/redwood/treehouse/StateSnapshot.kt b/redwood-treehouse/src/commonMain/kotlin/app/cash/redwood/treehouse/StateSnapshot.kt index a276588b6f..1c8cffa072 100644 --- a/redwood-treehouse/src/commonMain/kotlin/app/cash/redwood/treehouse/StateSnapshot.kt +++ b/redwood-treehouse/src/commonMain/kotlin/app/cash/redwood/treehouse/StateSnapshot.kt @@ -18,20 +18,28 @@ package app.cash.redwood.treehouse import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import kotlin.jvm.JvmInline +import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.booleanOrNull import kotlinx.serialization.json.doubleOrNull +import kotlinx.serialization.json.intOrNull @Serializable public class StateSnapshot( - public val content: Map>, + public val content: Map> ) { - public fun toValuesMap(): Map>? { + public fun toValuesMap(): Map> { return content.mapValues { entry -> - entry.value.map { mutableStateOf(it.fromJsonElement()) } + entry.value.map { + if (it.isMutableState) { + mutableStateOf(it.value) + } else { + it.value.fromJsonElement() + } + } } } @@ -46,10 +54,10 @@ public class StateSnapshot( */ public fun Map>.toStateSnapshot(): StateSnapshot = StateSnapshot( mapValues { entry -> - entry.value.map { element -> - when (element) { - is MutableState<*> -> element.value.toJsonElement() - else -> error("unexpected type: $this") + entry.value.map {element -> + when(element) { + is MutableState<*> -> Saveable(true, element.value.toJsonElement()) + else -> Saveable(false, element.toJsonElement()) } } }, @@ -58,6 +66,7 @@ public fun Map>.toStateSnapshot(): StateSnapshot = StateSnaps private fun Any?.toJsonElement(): JsonElement { return when (this) { is String -> JsonPrimitive(this) + is Int -> JsonPrimitive(this) is List<*> -> JsonArray(map { it.toJsonElement() }) is JsonElement -> this else -> error("unexpected type: $this") @@ -68,11 +77,9 @@ private fun Any?.toJsonElement(): JsonElement { private fun JsonElement?.fromJsonElement(): Any { return when (this) { is JsonPrimitive -> { - if (this.isString) { - return content - } - return booleanOrNull ?: doubleOrNull ?: error("unexpected type: $this") - // TODO add other primitive types (double, float, long) when needed + if (this.isString) { return content } + return booleanOrNull ?: doubleOrNull ?: intOrNull ?: error("unexpected type: $this") + // TODO add other primitive types (float, long) when needed } is JsonArray -> listOf({ this.forEach { it.toJsonElement() } }) @@ -81,3 +88,8 @@ private fun JsonElement?.fromJsonElement(): Any { else -> error("unexpected type: $this") } } + +public data class Saveable ( + val isMutableState: Boolean, + val value: JsonElement +) diff --git a/samples/emoji-search/presenter/src/commonMain/kotlin/com/example/redwood/emojisearch/presenter/EmojiSearch.kt b/samples/emoji-search/presenter/src/commonMain/kotlin/com/example/redwood/emojisearch/presenter/EmojiSearch.kt index 2406e2800c..caab15a775 100644 --- a/samples/emoji-search/presenter/src/commonMain/kotlin/com/example/redwood/emojisearch/presenter/EmojiSearch.kt +++ b/samples/emoji-search/presenter/src/commonMain/kotlin/com/example/redwood/emojisearch/presenter/EmojiSearch.kt @@ -36,6 +36,7 @@ import app.cash.redwood.layout.compose.Column import app.cash.redwood.layout.compose.Row import app.cash.redwood.lazylayout.compose.ExperimentalRedwoodLazyLayoutApi import app.cash.redwood.lazylayout.compose.LazyColumn +import app.cash.redwood.lazylayout.compose.LazyListState import app.cash.redwood.lazylayout.compose.items import app.cash.redwood.lazylayout.compose.rememberLazyListState import app.cash.redwood.ui.Margin @@ -96,7 +97,9 @@ private fun LazyColumn( override fun SaverScope.save(value: TextFieldState) = value.text } - var searchTerm by rememberSaveable(stateSaver = searchTermSaver) { mutableStateOf(TextFieldState("")) } + var searchTerm by rememberSaveable(stateSaver = searchTermSaver) { + mutableStateOf(TextFieldState("")) + } LaunchedEffect(refreshSignal) { try { @@ -173,7 +176,14 @@ private fun NestedFlexBoxContainers(httpClient: HttpClient, navigator: Navigator override fun SaverScope.save(value: TextFieldState) = value.text } - var searchTerm by rememberSaveable(stateSaver = searchTermSaver) { mutableStateOf(TextFieldState("")) } + var searchTerm by rememberSaveable(stateSaver = searchTermSaver) { + error("boom") + mutableStateOf(TextFieldState("")) + } + var searchTerm1 by rememberSaveable(stateSaver = searchTermSaver) { + error("boom") + mutableStateOf(TextFieldState("hhh")) + } LaunchedEffect(Unit) { try {