Merge "Add `MutableStateFlowSerializer` for serializing `MutableState`" into androidx-main
diff --git a/savedstate/savedstate/api/current.txt b/savedstate/savedstate/api/current.txt
index 8a63bfe..7249870 100644
--- a/savedstate/savedstate/api/current.txt
+++ b/savedstate/savedstate/api/current.txt
@@ -219,6 +219,11 @@
property public final kotlinx.serialization.descriptors.SerialDescriptor descriptor;
}
+ public final class MutableStateFlowSerializerKt {
+ method public static inline <reified T> kotlinx.serialization.KSerializer<kotlinx.coroutines.flow.MutableStateFlow<T>> MutableStateFlowSerializer();
+ method public static <T> kotlinx.serialization.KSerializer<kotlinx.coroutines.flow.MutableStateFlow<T>> MutableStateFlowSerializer(kotlinx.serialization.KSerializer<T> serializer);
+ }
+
public final class ParcelableArraySerializer implements kotlinx.serialization.KSerializer<android.os.Parcelable[]> {
ctor public ParcelableArraySerializer();
method public android.os.Parcelable[] deserialize(kotlinx.serialization.encoding.Decoder decoder);
diff --git a/savedstate/savedstate/api/restricted_current.txt b/savedstate/savedstate/api/restricted_current.txt
index 0f65840..c5c3df3 100644
--- a/savedstate/savedstate/api/restricted_current.txt
+++ b/savedstate/savedstate/api/restricted_current.txt
@@ -244,6 +244,11 @@
property public final kotlinx.serialization.descriptors.SerialDescriptor descriptor;
}
+ public final class MutableStateFlowSerializerKt {
+ method public static inline <reified T> kotlinx.serialization.KSerializer<kotlinx.coroutines.flow.MutableStateFlow<T>> MutableStateFlowSerializer();
+ method public static <T> kotlinx.serialization.KSerializer<kotlinx.coroutines.flow.MutableStateFlow<T>> MutableStateFlowSerializer(kotlinx.serialization.KSerializer<T> serializer);
+ }
+
public final class ParcelableArraySerializer implements kotlinx.serialization.KSerializer<android.os.Parcelable[]> {
ctor public ParcelableArraySerializer();
method public android.os.Parcelable[] deserialize(kotlinx.serialization.encoding.Decoder decoder);
diff --git a/savedstate/savedstate/bcv/native/current.txt b/savedstate/savedstate/bcv/native/current.txt
index 2f3971d..e96770a 100644
--- a/savedstate/savedstate/bcv/native/current.txt
+++ b/savedstate/savedstate/bcv/native/current.txt
@@ -165,12 +165,14 @@
final fun <#A: kotlin/Any> (androidx.savedstate/SavedStateRegistryOwner).androidx.savedstate.serialization/saved(kotlinx.serialization/KSerializer<#A>, kotlin/Function0<#A>): kotlin.properties/ReadWriteProperty<kotlin/Any?, #A> // androidx.savedstate.serialization/saved|[email protected](kotlinx.serialization.KSerializer<0:0>;kotlin.Function0<0:0>){0§<kotlin.Any>}[0]
final fun <#A: kotlin/Any> androidx.savedstate.serialization/decodeFromSavedState(kotlinx.serialization/DeserializationStrategy<#A>, androidx.savedstate/SavedState): #A // androidx.savedstate.serialization/decodeFromSavedState|decodeFromSavedState(kotlinx.serialization.DeserializationStrategy<0:0>;androidx.savedstate.SavedState){0§<kotlin.Any>}[0]
final fun <#A: kotlin/Any> androidx.savedstate.serialization/encodeToSavedState(kotlinx.serialization/SerializationStrategy<#A>, #A): androidx.savedstate/SavedState // androidx.savedstate.serialization/encodeToSavedState|encodeToSavedState(kotlinx.serialization.SerializationStrategy<0:0>;0:0){0§<kotlin.Any>}[0]
+final fun <#A: kotlin/Any?> androidx.savedstate.serialization.serializers/MutableStateFlowSerializer(kotlinx.serialization/KSerializer<#A>): kotlinx.serialization/KSerializer<kotlinx.coroutines.flow/MutableStateFlow<#A>> // androidx.savedstate.serialization.serializers/MutableStateFlowSerializer|MutableStateFlowSerializer(kotlinx.serialization.KSerializer<0:0>){0§<kotlin.Any?>}[0]
final inline fun <#A: kotlin/Any?> (androidx.savedstate/SavedState).androidx.savedstate/read(kotlin/Function1<androidx.savedstate/SavedStateReader, #A>): #A // androidx.savedstate/read|[email protected](kotlin.Function1<androidx.savedstate.SavedStateReader,0:0>){0§<kotlin.Any?>}[0]
final inline fun <#A: kotlin/Any?> (androidx.savedstate/SavedState).androidx.savedstate/write(kotlin/Function1<androidx.savedstate/SavedStateWriter, #A>): #A // androidx.savedstate/write|[email protected](kotlin.Function1<androidx.savedstate.SavedStateWriter,0:0>){0§<kotlin.Any?>}[0]
final inline fun <#A: reified kotlin/Any> (androidx.savedstate/SavedStateRegistryOwner).androidx.savedstate.serialization/saved(kotlin/String, noinline kotlin/Function0<#A>): kotlin.properties/ReadWriteProperty<kotlin/Any?, #A> // androidx.savedstate.serialization/saved|[email protected](kotlin.String;kotlin.Function0<0:0>){0§<kotlin.Any>}[0]
final inline fun <#A: reified kotlin/Any> (androidx.savedstate/SavedStateRegistryOwner).androidx.savedstate.serialization/saved(noinline kotlin/Function0<#A>): kotlin.properties/ReadWriteProperty<kotlin/Any?, #A> // androidx.savedstate.serialization/saved|[email protected](kotlin.Function0<0:0>){0§<kotlin.Any>}[0]
final inline fun <#A: reified kotlin/Any> androidx.savedstate.serialization/decodeFromSavedState(androidx.savedstate/SavedState): #A // androidx.savedstate.serialization/decodeFromSavedState|decodeFromSavedState(androidx.savedstate.SavedState){0§<kotlin.Any>}[0]
final inline fun <#A: reified kotlin/Any> androidx.savedstate.serialization/encodeToSavedState(#A): androidx.savedstate/SavedState // androidx.savedstate.serialization/encodeToSavedState|encodeToSavedState(0:0){0§<kotlin.Any>}[0]
+final inline fun <#A: reified kotlin/Any?> androidx.savedstate.serialization.serializers/MutableStateFlowSerializer(): kotlinx.serialization/KSerializer<kotlinx.coroutines.flow/MutableStateFlow<#A>> // androidx.savedstate.serialization.serializers/MutableStateFlowSerializer|MutableStateFlowSerializer(){0§<kotlin.Any?>}[0]
final inline fun androidx.savedstate/keyNotFoundError(kotlin/String): kotlin/Nothing // androidx.savedstate/keyNotFoundError|keyNotFoundError(kotlin.String){}[0]
final inline fun androidx.savedstate/savedState(kotlin.collections/Map<kotlin/String, kotlin/Any?> = ..., kotlin/Function1<androidx.savedstate/SavedStateWriter, kotlin/Unit> = ...): androidx.savedstate/SavedState // androidx.savedstate/savedState|savedState(kotlin.collections.Map<kotlin.String,kotlin.Any?>;kotlin.Function1<androidx.savedstate.SavedStateWriter,kotlin.Unit>){}[0]
final inline fun androidx.savedstate/valueNotFoundError(kotlin/String): kotlin/Nothing // androidx.savedstate/valueNotFoundError|valueNotFoundError(kotlin.String){}[0]
diff --git a/savedstate/savedstate/build.gradle b/savedstate/savedstate/build.gradle
index b3e7304..9d326c2 100644
--- a/savedstate/savedstate/build.gradle
+++ b/savedstate/savedstate/build.gradle
@@ -32,6 +32,7 @@
api(libs.kotlinStdlib)
api("androidx.annotation:annotation:1.8.0")
api(projectOrArtifact(":lifecycle:lifecycle-common"))
+ api(libs.kotlinCoroutinesCore)
api(libs.kotlinSerializationCore)
}
}
diff --git a/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/serialization/serializers/MutableStateFlowSerializer.kt b/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/serialization/serializers/MutableStateFlowSerializer.kt
new file mode 100644
index 0000000..eb076e05
--- /dev/null
+++ b/savedstate/savedstate/src/commonMain/kotlin/androidx/savedstate/serialization/serializers/MutableStateFlowSerializer.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2025 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
+ *
+ * http://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.
+ */
+
+@file:OptIn(InternalSerializationApi::class, ExperimentalTypeInference::class)
+
+package androidx.savedstate.serialization.serializers
+
+import kotlin.experimental.ExperimentalTypeInference
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.serialization.InternalSerializationApi
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+import kotlinx.serialization.serializer
+
+/**
+ * Creates a [KSerializer] for a [MutableStateFlow] containing a [Serializable] value of type [T].
+ *
+ * This inline function infers the state type [T] automatically and retrieves the appropriate
+ * [KSerializer] for serialization and deserialization of [MutableStateFlow].
+ *
+ * @param T The type of the value stored in the [MutableStateFlow].
+ * @return A [KSerializer] for handling [MutableStateFlow] containing a [Serializable] type [T].
+ */
+public inline fun <reified T> MutableStateFlowSerializer(): KSerializer<MutableStateFlow<T>> {
+ return MutableStateFlowSerializer(serializer())
+}
+
+/**
+ * Creates a [KSerializer] for a [MutableStateFlow] containing a [Serializable] value of type [T].
+ *
+ * This function allows for explicit specification of the [KSerializer] for the state type [T]. It
+ * provides serialization and deserialization capabilities for [MutableStateFlow] objects.
+ *
+ * @param T The type of the value stored in the [MutableStateFlow].
+ * @param serializer The [KSerializer] for the [Serializable] type [T].
+ * @return A [KSerializer] for handling [MutableStateFlow] containing a [Serializable] type [T].
+ */
+public fun <T> MutableStateFlowSerializer(
+ serializer: KSerializer<T>
+): KSerializer<MutableStateFlow<T>> {
+ return MutableStateFlowSerializerImpl<T>(serializer)
+}
+
+/**
+ * Internal implementation of [KSerializer] for [MutableStateFlow].
+ *
+ * This private class wraps a [KSerializer] for the inner value type [T], enabling serialization and
+ * deserialization of [MutableStateFlow] instances. The inner value serialization is delegated to
+ * the provided [valueSerializer].
+ *
+ * @param T The type of the value stored in the [MutableStateFlow].
+ * @property valueSerializer The [KSerializer] used to serialize and deserialize the inner value.
+ */
+private class MutableStateFlowSerializerImpl<T>(
+ private val valueSerializer: KSerializer<T>,
+) : KSerializer<MutableStateFlow<T>> {
+
+ override val descriptor: SerialDescriptor = valueSerializer.descriptor
+
+ override fun serialize(encoder: Encoder, value: MutableStateFlow<T>) {
+ valueSerializer.serialize(encoder, value.value)
+ }
+
+ override fun deserialize(decoder: Decoder): MutableStateFlow<T> {
+ return MutableStateFlow(valueSerializer.deserialize(decoder))
+ }
+}
diff --git a/savedstate/savedstate/src/commonTest/kotlin/androidx/savedstate/serialization/MutableStateFlowSerializerTest.kt b/savedstate/savedstate/src/commonTest/kotlin/androidx/savedstate/serialization/MutableStateFlowSerializerTest.kt
new file mode 100644
index 0000000..1c38889
--- /dev/null
+++ b/savedstate/savedstate/src/commonTest/kotlin/androidx/savedstate/serialization/MutableStateFlowSerializerTest.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2025 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
+ *
+ * http://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 androidx.savedstate.serialization
+
+import androidx.kruth.assertThat
+import androidx.savedstate.RobolectricTest
+import androidx.savedstate.serialization.serializers.MutableStateFlowSerializer
+import kotlin.test.Test
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.serialization.InternalSerializationApi
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.serializer
+
+internal class MutableStateFlowSerializerTest : RobolectricTest() {
+
+ @Test
+ fun encodeDecode_withImplicitSerializer() {
+ val state = MutableStateFlow(USER_JOHN_DOE)
+ val serializer = MutableStateFlowSerializer<User>()
+
+ val encoded = encodeToSavedState(serializer, state)
+ val decoded = decodeFromSavedState(serializer, encoded)
+
+ assertThat(state.value).isEqualTo(decoded.value)
+ }
+
+ @Test
+ fun encodeDecode_withExplicitSerializer() {
+ val state = MutableStateFlow(USER_JOHN_DOE)
+ val serializer = MutableStateFlowSerializer(USER_SERIALIZER)
+
+ val encoded = encodeToSavedState(serializer, state)
+ val decoded = decodeFromSavedState(serializer, encoded)
+
+ assertThat(state.value).isEqualTo(decoded.value)
+ }
+
+ companion object {
+ val USER_JOHN_DOE = User(name = "John", surname = "Doe")
+ @OptIn(InternalSerializationApi::class) val USER_SERIALIZER = User::class.serializer()
+ }
+
+ @Serializable data class User(val name: String = "John", val surname: String = "Doe")
+}