Merge changes from topic "serialization-mutable-state" into androidx-main
* changes:
Add `MutableStateSerializer` for serializing `MutableState`
Move KMP compatible test dependencies to `commonTest`
diff --git a/lifecycle/lifecycle-viewmodel-compose/api/current.txt b/lifecycle/lifecycle-viewmodel-compose/api/current.txt
index ace8748..8372225 100644
--- a/lifecycle/lifecycle-viewmodel-compose/api/current.txt
+++ b/lifecycle/lifecycle-viewmodel-compose/api/current.txt
@@ -29,3 +29,12 @@
}
+package androidx.lifecycle.viewmodel.compose.serialization.serializers {
+
+ public final class MutableStateSerializerKt {
+ method public static inline <reified T> kotlinx.serialization.KSerializer<androidx.compose.runtime.MutableState<T>> MutableStateSerializer();
+ method public static <T> kotlinx.serialization.KSerializer<androidx.compose.runtime.MutableState<T>> MutableStateSerializer(kotlinx.serialization.KSerializer<T> serializer);
+ }
+
+}
+
diff --git a/lifecycle/lifecycle-viewmodel-compose/api/restricted_current.txt b/lifecycle/lifecycle-viewmodel-compose/api/restricted_current.txt
index ace8748..8372225 100644
--- a/lifecycle/lifecycle-viewmodel-compose/api/restricted_current.txt
+++ b/lifecycle/lifecycle-viewmodel-compose/api/restricted_current.txt
@@ -29,3 +29,12 @@
}
+package androidx.lifecycle.viewmodel.compose.serialization.serializers {
+
+ public final class MutableStateSerializerKt {
+ method public static inline <reified T> kotlinx.serialization.KSerializer<androidx.compose.runtime.MutableState<T>> MutableStateSerializer();
+ method public static <T> kotlinx.serialization.KSerializer<androidx.compose.runtime.MutableState<T>> MutableStateSerializer(kotlinx.serialization.KSerializer<T> serializer);
+ }
+
+}
+
diff --git a/lifecycle/lifecycle-viewmodel-compose/build.gradle b/lifecycle/lifecycle-viewmodel-compose/build.gradle
index c63ee18..6b1230c 100644
--- a/lifecycle/lifecycle-viewmodel-compose/build.gradle
+++ b/lifecycle/lifecycle-viewmodel-compose/build.gradle
@@ -30,6 +30,7 @@
id("AndroidXPlugin")
id("com.android.library")
id("AndroidXComposePlugin")
+ alias(libs.plugins.kotlinSerialization)
}
androidXMultiplatform {
@@ -45,12 +46,17 @@
api(project(":lifecycle:lifecycle-viewmodel"))
api("androidx.annotation:annotation:1.8.1")
api("androidx.compose.runtime:runtime:1.6.0")
+ api(libs.kotlinSerializationCore)
implementation(libs.kotlinStdlib)
}
}
commonTest {
- // TODO(b/330323282): Move common dependencies here.
+ dependencies {
+ implementation(project(":lifecycle:lifecycle-viewmodel-savedstate"))
+ implementation(project(":lifecycle:lifecycle-viewmodel-testing"))
+ implementation(project(":lifecycle:lifecycle-runtime-testing"))
+ }
}
androidMain {
@@ -83,9 +89,7 @@
// but it doesn't work in androidx.
// See aosp/1804059
implementation(project(":lifecycle:lifecycle-common-java8"))
- implementation(project(":lifecycle:lifecycle-viewmodel-savedstate"))
implementation(project(":activity:activity-compose"))
- implementation(project(":lifecycle:lifecycle-runtime-testing"))
}
}
}
diff --git a/lifecycle/lifecycle-viewmodel-compose/src/androidInstrumentedTest/kotlin/androidx/lifecycle/viewmodel/compose/serialization/serializers/MutableStateSerializerTest.android.kt b/lifecycle/lifecycle-viewmodel-compose/src/androidInstrumentedTest/kotlin/androidx/lifecycle/viewmodel/compose/serialization/serializers/MutableStateSerializerTest.android.kt
new file mode 100644
index 0000000..0cbeb73
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel-compose/src/androidInstrumentedTest/kotlin/androidx/lifecycle/viewmodel/compose/serialization/serializers/MutableStateSerializerTest.android.kt
@@ -0,0 +1,63 @@
+/*
+ * 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.lifecycle.viewmodel.compose.serialization.serializers
+
+import androidx.compose.runtime.mutableStateOf
+import androidx.savedstate.serialization.decodeFromSavedState
+import androidx.savedstate.serialization.encodeToSavedState
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import kotlinx.serialization.InternalSerializationApi
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.serializer
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MutableStateSerializerTest {
+
+ @Test
+ fun encodeDecode_withImplicitSerializer() {
+ val state = mutableStateOf(USER_JOHN_DOE)
+ val serializer = MutableStateSerializer<User>()
+
+ val encoded = encodeToSavedState(serializer, state)
+ val decoded = decodeFromSavedState(serializer, encoded)
+
+ assertThat(state.value).isEqualTo(decoded.value)
+ }
+
+ @Test
+ fun encodeDecode_withExplicitSerializer() {
+ val state = mutableStateOf(USER_JOHN_DOE)
+ val serializer = MutableStateSerializer(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")
+}
diff --git a/lifecycle/lifecycle-viewmodel-compose/src/commonMain/kotlin/androidx/lifecycle/viewmodel/compose/serialization/serializers/MutableStateSerializer.kt b/lifecycle/lifecycle-viewmodel-compose/src/commonMain/kotlin/androidx/lifecycle/viewmodel/compose/serialization/serializers/MutableStateSerializer.kt
new file mode 100644
index 0000000..1bd43bd
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel-compose/src/commonMain/kotlin/androidx/lifecycle/viewmodel/compose/serialization/serializers/MutableStateSerializer.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.lifecycle.viewmodel.compose.serialization.serializers
+
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import kotlin.experimental.ExperimentalTypeInference
+import kotlinx.serialization.InternalSerializationApi
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+import kotlinx.serialization.serializer
+
+/**
+ * Creates a [KSerializer] for a [MutableState] 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 [MutableState].
+ *
+ * @param T The type of the value stored in the [MutableState].
+ * @return A [KSerializer] for handling [MutableState] containing a [Serializable] type [T].
+ */
+@Suppress("FunctionName")
+public inline fun <reified T> MutableStateSerializer(): KSerializer<MutableState<T>> {
+ return MutableStateSerializer(serializer())
+}
+
+/**
+ * Creates a [KSerializer] for a [MutableState] 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 [MutableState] objects.
+ *
+ * @param T The type of the value stored in the [MutableState].
+ * @param serializer The [KSerializer] for the [Serializable] type [T].
+ * @return A [KSerializer] for handling [MutableState] containing a [Serializable] type [T].
+ */
+@Suppress("FunctionName")
+public fun <T> MutableStateSerializer(serializer: KSerializer<T>): KSerializer<MutableState<T>> {
+ return MutableStateSerializerImpl<T>(serializer)
+}
+
+/**
+ * Internal implementation of [KSerializer] for [MutableState].
+ *
+ * This private class wraps a [KSerializer] for the inner value type [T], enabling serialization and
+ * deserialization of [MutableState] instances. The inner value serialization is delegated to the
+ * provided [valueSerializer].
+ *
+ * @param T The type of the value stored in the [MutableState].
+ * @property valueSerializer The [KSerializer] used to serialize and deserialize the inner value.
+ */
+private class MutableStateSerializerImpl<T>(
+ private val valueSerializer: KSerializer<T>,
+) : KSerializer<MutableState<T>> {
+
+ override val descriptor: SerialDescriptor = valueSerializer.descriptor
+
+ override fun serialize(encoder: Encoder, value: MutableState<T>) {
+ valueSerializer.serialize(encoder, value.value)
+ }
+
+ override fun deserialize(decoder: Decoder): MutableState<T> {
+ return mutableStateOf(valueSerializer.deserialize(decoder))
+ }
+}