Skip to content

Commit

Permalink
Merge "Add property delegates to support saving and restoring seriali…
Browse files Browse the repository at this point in the history
…zable data with SavedStateHandle" into androidx-main
  • Loading branch information
marcellogalhardo authored and Gerrit Code Review committed Nov 6, 2024
2 parents 31a0290 + 56f2b18 commit a1c3ae7
Show file tree
Hide file tree
Showing 11 changed files with 419 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs-tip-of-tree/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ dependencies {
kmpDocs(project(":lifecycle:lifecycle-viewmodel-compose"))
docs(project(":lifecycle:lifecycle-viewmodel-ktx"))
kmpDocs(project(":lifecycle:lifecycle-viewmodel-savedstate"))
samples(project(":lifecycle:lifecycle-viewmodel-savedstate-samples"))
kmpDocs(project(":lifecycle:lifecycle-viewmodel-testing"))
docs(project(":loader:loader"))
docs(project(":loader:loader-ktx"))
Expand Down
52 changes: 52 additions & 0 deletions lifecycle/lifecycle-viewmodel-savedstate-samples/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright (C) 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
*
* 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.
*/

/**
* This file was created using the `create_project.py` script located in the
* `<AndroidX root>/development/project-creator` directory.
*
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/

import androidx.build.KotlinTarget
import androidx.build.LibraryType

plugins {
id("AndroidXPlugin")
id("com.android.library")
id("org.jetbrains.kotlin.android")
alias(libs.plugins.kotlinSerialization)
}

dependencies {
api(libs.kotlinStdlib)
compileOnly(project(":annotation:annotation-sampled"))
implementation(project(":lifecycle:lifecycle-viewmodel"))
implementation(project(":lifecycle:lifecycle-viewmodel-savedstate"))
}

android {
namespace "androidx.lifecycle.viewmodel.savedstate.samples"
}

androidx {
name = "androidx.lifecycle:lifecycle-viewmodel-savedstate-samples"
type = LibraryType.SAMPLES
inceptionYear = "2024"
description = "Lifecycle ViewModel SavedState Samples"
kotlinTarget = KotlinTarget.KOTLIN_1_9
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* 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
*
* 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

import androidx.annotation.Sampled
import kotlinx.serialization.InternalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.serializer

@Sampled
fun delegate() {
@Serializable data class User(val id: Int, val name: String)
class ProfileViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
val user by savedStateHandle.saved { User(123, "foo") }
}
}

@Sampled
fun delegateExplicitKey() {
@Serializable data class User(val id: Int, val name: String)
class ProfileViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
val user by savedStateHandle.saved(key = "bar") { User(123, "foo") }
}
}

@OptIn(InternalSerializationApi::class)
@Sampled
fun delegateExplicitSerializer() {
@Serializable data class User(val id: Int, val name: String)
class ProfileViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
val user by savedStateHandle.saved(User::class.serializer()) { User(123, "foo") }
}
}

@OptIn(InternalSerializationApi::class)
@Sampled
fun delegateExplicitKeyAndSerializer() {
@Serializable data class User(val id: Int, val name: String)
class ProfileViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
val user by
savedStateHandle.saved(key = "bar", serializer = User::class.serializer()) {
User(123, "foo")
}
}
}
7 changes: 7 additions & 0 deletions lifecycle/lifecycle-viewmodel-savedstate/api/current.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ package androidx.lifecycle {
public static final class SavedStateHandle.Companion {
}

public final class SavedStateHandleDelegatesKt {
method public static inline <reified T> kotlin.properties.ReadWriteProperty<java.lang.Object?,T> saved(androidx.lifecycle.SavedStateHandle, String key, kotlin.jvm.functions.Function0<? extends T> init);
method public static <T> kotlin.properties.ReadWriteProperty<java.lang.Object?,T> saved(androidx.lifecycle.SavedStateHandle, String key, kotlinx.serialization.KSerializer<T> serializer, kotlin.jvm.functions.Function0<? extends T> init);
method public static inline <reified T> kotlin.properties.ReadWriteProperty<java.lang.Object?,T> saved(androidx.lifecycle.SavedStateHandle, kotlin.jvm.functions.Function0<? extends T> init);
method public static <T> kotlin.properties.ReadWriteProperty<java.lang.Object?,T> saved(androidx.lifecycle.SavedStateHandle, kotlinx.serialization.KSerializer<T> serializer, kotlin.jvm.functions.Function0<? extends T> init);
}

public final class SavedStateHandleSupport {
method @MainThread public static androidx.lifecycle.SavedStateHandle createSavedStateHandle(androidx.lifecycle.viewmodel.CreationExtras);
method @MainThread public static <T extends androidx.savedstate.SavedStateRegistryOwner & androidx.lifecycle.ViewModelStoreOwner> void enableSavedStateHandles(T);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ package androidx.lifecycle {
public static final class SavedStateHandle.Companion {
}

public final class SavedStateHandleDelegatesKt {
method public static inline <reified T> kotlin.properties.ReadWriteProperty<java.lang.Object?,T> saved(androidx.lifecycle.SavedStateHandle, String key, kotlin.jvm.functions.Function0<? extends T> init);
method public static <T> kotlin.properties.ReadWriteProperty<java.lang.Object?,T> saved(androidx.lifecycle.SavedStateHandle, String key, kotlinx.serialization.KSerializer<T> serializer, kotlin.jvm.functions.Function0<? extends T> init);
method public static inline <reified T> kotlin.properties.ReadWriteProperty<java.lang.Object?,T> saved(androidx.lifecycle.SavedStateHandle, kotlin.jvm.functions.Function0<? extends T> init);
method public static <T> kotlin.properties.ReadWriteProperty<java.lang.Object?,T> saved(androidx.lifecycle.SavedStateHandle, kotlinx.serialization.KSerializer<T> serializer, kotlin.jvm.functions.Function0<? extends T> init);
}

public final class SavedStateHandleSupport {
method @MainThread public static androidx.lifecycle.SavedStateHandle createSavedStateHandle(androidx.lifecycle.viewmodel.CreationExtras);
method @MainThread public static <T extends androidx.savedstate.SavedStateRegistryOwner & androidx.lifecycle.ViewModelStoreOwner> void enableSavedStateHandles(T);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,7 @@ final val androidx.lifecycle/VIEW_MODEL_STORE_OWNER_KEY // androidx.lifecycle/VI

final fun (androidx.lifecycle.viewmodel/CreationExtras).androidx.lifecycle/createSavedStateHandle(): androidx.lifecycle/SavedStateHandle // androidx.lifecycle/createSavedStateHandle|createSavedStateHandle@androidx.lifecycle.viewmodel.CreationExtras(){}[0]
final fun <#A: androidx.lifecycle/ViewModelStoreOwner & androidx.savedstate/SavedStateRegistryOwner> (#A).androidx.lifecycle/enableSavedStateHandles() // androidx.lifecycle/enableSavedStateHandles|enableSavedStateHandles@0:0(){0§<androidx.savedstate.SavedStateRegistryOwner&androidx.lifecycle.ViewModelStoreOwner>}[0]
final fun <#A: kotlin/Any> (androidx.lifecycle/SavedStateHandle).androidx.lifecycle/saved(kotlin/String, kotlinx.serialization/KSerializer<#A>, kotlin/Function0<#A>): kotlin.properties/ReadWriteProperty<kotlin/Any?, #A> // androidx.lifecycle/saved|[email protected](kotlin.String;kotlinx.serialization.KSerializer<0:0>;kotlin.Function0<0:0>){0§<kotlin.Any>}[0]
final fun <#A: kotlin/Any> (androidx.lifecycle/SavedStateHandle).androidx.lifecycle/saved(kotlinx.serialization/KSerializer<#A>, kotlin/Function0<#A>): kotlin.properties/ReadWriteProperty<kotlin/Any?, #A> // androidx.lifecycle/saved|[email protected](kotlinx.serialization.KSerializer<0:0>;kotlin.Function0<0:0>){0§<kotlin.Any>}[0]
final inline fun <#A: reified kotlin/Any> (androidx.lifecycle/SavedStateHandle).androidx.lifecycle/saved(kotlin/String, noinline kotlin/Function0<#A>): kotlin.properties/ReadWriteProperty<kotlin/Any?, #A> // androidx.lifecycle/saved|[email protected](kotlin.String;kotlin.Function0<0:0>){0§<kotlin.Any>}[0]
final inline fun <#A: reified kotlin/Any> (androidx.lifecycle/SavedStateHandle).androidx.lifecycle/saved(noinline kotlin/Function0<#A>): kotlin.properties/ReadWriteProperty<kotlin/Any?, #A> // androidx.lifecycle/saved|[email protected](kotlin.Function0<0:0>){0§<kotlin.Any>}[0]
3 changes: 3 additions & 0 deletions lifecycle/lifecycle-viewmodel-savedstate/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode
plugins {
id("AndroidXPlugin")
id("com.android.library")
alias(libs.plugins.kotlinSerialization)
}

androidXMultiplatform {
Expand All @@ -49,6 +50,7 @@ androidXMultiplatform {
api(project(":lifecycle:lifecycle-viewmodel"))
api(libs.kotlinStdlib)
api(libs.kotlinCoroutinesCore)
api(libs.kotlinSerializationCore)
}
}

Expand Down Expand Up @@ -86,6 +88,7 @@ androidXMultiplatform {
implementation project(":lifecycle:lifecycle-livedata-core")
implementation ("androidx.fragment:fragment:1.3.0")
implementation project(":internal-testutils-runtime")
implementation(project(":lifecycle:lifecycle-viewmodel"))
implementation(libs.truth)
implementation(libs.testExtJunit)
implementation(libs.testCore)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
android:name="androidx.lifecycle.viewmodel.savedstate.FakingSavedStateActivity"/>
<activity
android:name="androidx.lifecycle.viewmodel.savedstate.SavedStateFactoryTest$MyActivity"/>
<activity
android:name="androidx.activity.ComponentActivity"/>
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*
* Copyright 2020 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

import androidx.activity.ComponentActivity
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.google.common.truth.Truth.assertThat
import kotlinx.serialization.builtins.serializer
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidJUnit4::class)
class SerializationTest {

@get:Rule val activityTestRuleScenario = ActivityScenarioRule(ComponentActivity::class.java)

@Test
fun simpleRestore() {
activityTestRuleScenario.scenario.onActivity { activity ->
val viewModel: MyViewModel = ViewModelProvider(activity)[MyViewModel::class]
var value by viewModel.savedStateHandle.saved { 1 }
assertThat(value).isEqualTo(1)
value = 2
assertThat(value).isEqualTo(2)
}
activityTestRuleScenario.scenario.recreate()
activityTestRuleScenario.scenario.onActivity { activity ->
val viewModel: MyViewModel = ViewModelProvider(activity)[MyViewModel::class]
var value: Int by
viewModel.savedStateHandle.saved { error("Unexpected initializer call") }
assertThat(value).isEqualTo(2)
}
}

@Test
fun explicitKey() {
activityTestRuleScenario.scenario.onActivity { activity ->
val viewModel: MyViewModel = ViewModelProvider(activity)[MyViewModel::class]
var value by viewModel.savedStateHandle.saved(key = "foo") { 1 }
assertThat(value).isEqualTo(1)
value = 2
assertThat(value).isEqualTo(2)
}
activityTestRuleScenario.scenario.recreate()
activityTestRuleScenario.scenario.onActivity { activity ->
val viewModel: MyViewModel = ViewModelProvider(activity)[MyViewModel::class]
var value: Int by
viewModel.savedStateHandle.saved(key = "foo") {
error("Unexpected initializer call")
}
assertThat(value).isEqualTo(2)
}
}

@Test
fun explicitSerializer() {
activityTestRuleScenario.scenario.onActivity { activity ->
val viewModel: MyViewModel = ViewModelProvider(activity)[MyViewModel::class]
val value by viewModel.savedStateHandle.saved(serializer = Int.serializer()) { 1 }
assertThat(value).isEqualTo(1)
}
activityTestRuleScenario.scenario.recreate()
activityTestRuleScenario.scenario.onActivity { activity ->
val viewModel: MyViewModel = ViewModelProvider(activity)[MyViewModel::class]
val value: Int by
viewModel.savedStateHandle.saved(serializer = Int.serializer()) {
error("Unexpected initializer call")
}
assertThat(value).isEqualTo(1)
}
}

@Test
fun explicitKeyAndSerializer() {
activityTestRuleScenario.scenario.onActivity { activity ->
val viewModel: MyViewModel = ViewModelProvider(activity)[MyViewModel::class]
val value by
viewModel.savedStateHandle.saved(key = "foo", serializer = Int.serializer()) { 1 }
assertThat(value).isEqualTo(1)
}
activityTestRuleScenario.scenario.recreate()
activityTestRuleScenario.scenario.onActivity { activity ->
val viewModel: MyViewModel = ViewModelProvider(activity)[MyViewModel::class]
val value: Int by
viewModel.savedStateHandle.saved(key = "foo", serializer = Int.serializer()) {
error("Unexpected initializer call")
}
assertThat(value).isEqualTo(1)
}
}

@Test
fun duplicateKeys() {
activityTestRuleScenario.scenario.onActivity { activity ->
val viewModel: MyViewModel = ViewModelProvider(activity)[MyViewModel::class]
val serializable by viewModel.savedStateHandle.saved(key = "foo") { 1 }
assertThat(serializable).isEqualTo(1)
val duplicate by viewModel.savedStateHandle.saved(key = "foo") { 2 }
assertThat(duplicate).isEqualTo(2)
// The value is from the initializer.
assertThat(serializable).isEqualTo(1)
}
activityTestRuleScenario.scenario.recreate()
activityTestRuleScenario.scenario.onActivity { activity ->
val viewModel: MyViewModel = ViewModelProvider(activity)[MyViewModel::class]
val serializable: Int by
viewModel.savedStateHandle.saved(key = "foo") {
error("Unexpected initializer call")
}
assertThat(serializable).isEqualTo(2)
val duplicate: Int by
viewModel.savedStateHandle.saved(key = "foo") {
error("Unexpected initializer call")
}
assertThat(duplicate).isEqualTo(2)
assertThat(serializable).isEqualTo(2)
}
}
}

class MyViewModel(val savedStateHandle: SavedStateHandle) : ViewModel()
Loading

0 comments on commit a1c3ae7

Please sign in to comment.