Introduce CreationExtras
It also passes key associated with ViewModel
as an extra.
Metalava incorrectly says that this is a breaking change: b/188691010
Relnote: "ViewModelProvider.CreationExtras were introduced.
This API will simplify passing an additional information to ViewModelProvider.Factory"
bug: 188541057
Test: ViewModelProviderTest
Change-Id: Ia73439cb2282609a9a1eaebf8ba79b9cc93feb7c
diff --git a/lifecycle/lifecycle-viewmodel-compose/build.gradle b/lifecycle/lifecycle-viewmodel-compose/build.gradle
index d389e76..30a05a0 100644
--- a/lifecycle/lifecycle-viewmodel-compose/build.gradle
+++ b/lifecycle/lifecycle-viewmodel-compose/build.gradle
@@ -17,6 +17,7 @@
import androidx.build.LibraryGroups
import androidx.build.Publish
import androidx.build.RunApiTasks
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("AndroidXPlugin")
@@ -53,3 +54,15 @@
description = "Compose integration with Lifecycle ViewModel"
runApiTasks = new RunApiTasks.Yes()
}
+
+// needed only while https://youtrack.jetbrains.com/issue/KT-47000 isn't resolved which is
+// targeted to 1.6
+if (project.hasProperty("androidx.useMaxDepVersions")){
+ tasks.withType(KotlinCompile).configureEach {
+ kotlinOptions {
+ freeCompilerArgs += [
+ "-Xjvm-default=enable",
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/api/current.ignore b/lifecycle/lifecycle-viewmodel-savedstate/api/current.ignore
new file mode 100644
index 0000000..be07271
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel-savedstate/api/current.ignore
@@ -0,0 +1,3 @@
+// Baseline format: 1.0
+RemovedMethod: androidx.lifecycle.SavedStateViewModelFactory#create(Class<T>):
+ Removed method androidx.lifecycle.SavedStateViewModelFactory.create(Class<T>)
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/api/current.txt b/lifecycle/lifecycle-viewmodel-savedstate/api/current.txt
index f830aba..1447260 100644
--- a/lifecycle/lifecycle-viewmodel-savedstate/api/current.txt
+++ b/lifecycle/lifecycle-viewmodel-savedstate/api/current.txt
@@ -25,7 +25,6 @@
ctor public SavedStateViewModelFactory(android.app.Application?, androidx.savedstate.SavedStateRegistryOwner);
ctor public SavedStateViewModelFactory(android.app.Application?, androidx.savedstate.SavedStateRegistryOwner, android.os.Bundle?);
method public <T extends androidx.lifecycle.ViewModel> T create(String, Class<T!>);
- method public <T extends androidx.lifecycle.ViewModel> T create(Class<T!>);
}
}
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/api/public_plus_experimental_current.txt b/lifecycle/lifecycle-viewmodel-savedstate/api/public_plus_experimental_current.txt
index f830aba..1447260 100644
--- a/lifecycle/lifecycle-viewmodel-savedstate/api/public_plus_experimental_current.txt
+++ b/lifecycle/lifecycle-viewmodel-savedstate/api/public_plus_experimental_current.txt
@@ -25,7 +25,6 @@
ctor public SavedStateViewModelFactory(android.app.Application?, androidx.savedstate.SavedStateRegistryOwner);
ctor public SavedStateViewModelFactory(android.app.Application?, androidx.savedstate.SavedStateRegistryOwner, android.os.Bundle?);
method public <T extends androidx.lifecycle.ViewModel> T create(String, Class<T!>);
- method public <T extends androidx.lifecycle.ViewModel> T create(Class<T!>);
}
}
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/build.gradle b/lifecycle/lifecycle-viewmodel-savedstate/build.gradle
index 85e49e0..7f9470d 100644
--- a/lifecycle/lifecycle-viewmodel-savedstate/build.gradle
+++ b/lifecycle/lifecycle-viewmodel-savedstate/build.gradle
@@ -38,18 +38,8 @@
androidTestImplementation projectOrArtifact(":lifecycle:lifecycle-runtime")
androidTestImplementation projectOrArtifact(":lifecycle:lifecycle-livedata-core")
- androidTestImplementation ("androidx.fragment:fragment:1.3.0") {
- exclude group: "androidx.lifecycle", module: "lifecycle-runtime"
- exclude group: "androidx.lifecycle", module: "lifecycle-livedata-core"
- exclude group: "androidx.lifecycle", module: "lifecycle-viewmodel-savedstate"
- exclude group: "androidx.lifecycle", module: "lifecycle-viewmodel"
- }
- androidTestImplementation project(":internal-testutils-runtime"), {
- exclude group: "androidx.lifecycle", module: "lifecycle-runtime"
- exclude group: "androidx.lifecycle", module: "lifecycle-livedata-core"
- exclude group: "androidx.lifecycle", module: "lifecycle-viewmodel-savedstate"
- exclude group: "androidx.lifecycle", module: "lifecycle-viewmodel"
- }
+ androidTestImplementation ("androidx.fragment:fragment:1.3.0")
+ androidTestImplementation project(":internal-testutils-runtime")
androidTestImplementation(libs.truth)
androidTestImplementation(libs.kotlinStdlib)
androidTestImplementation(libs.testExtJunit)
diff --git a/lifecycle/lifecycle-viewmodel/api/current.txt b/lifecycle/lifecycle-viewmodel/api/current.txt
index b2a64d8..d63c190 100644
--- a/lifecycle/lifecycle-viewmodel/api/current.txt
+++ b/lifecycle/lifecycle-viewmodel/api/current.txt
@@ -41,13 +41,14 @@
}
public static interface ViewModelProvider.Factory {
- method public <T extends androidx.lifecycle.ViewModel> T create(Class<T> modelClass);
+ method public default <T extends androidx.lifecycle.ViewModel> T create(Class<T> modelClass);
+ method public default <T extends androidx.lifecycle.ViewModel> T create(Class<T> modelClass, androidx.lifecycle.viewmodel.CreationExtras extras);
}
public static class ViewModelProvider.NewInstanceFactory implements androidx.lifecycle.ViewModelProvider.Factory {
ctor public ViewModelProvider.NewInstanceFactory();
- method public <T extends androidx.lifecycle.ViewModel> T create(Class<T> modelClass);
field public static final androidx.lifecycle.ViewModelProvider.NewInstanceFactory.Companion Companion;
+ field public static final androidx.lifecycle.viewmodel.CreationExtras.Key<java.lang.String> VIEW_MODEL_KEY;
}
public static final class ViewModelProvider.NewInstanceFactory.Companion {
@@ -77,3 +78,20 @@
}
+package androidx.lifecycle.viewmodel {
+
+ public interface CreationExtras {
+ method public operator <T> T? get(androidx.lifecycle.viewmodel.CreationExtras.Key<T> key);
+ }
+
+ public static interface CreationExtras.Key<T> {
+ }
+
+ public final class MutableCreationExtras implements androidx.lifecycle.viewmodel.CreationExtras {
+ ctor public MutableCreationExtras();
+ method public <T> T? get(androidx.lifecycle.viewmodel.CreationExtras.Key<T> key);
+ method public operator <T> void set(androidx.lifecycle.viewmodel.CreationExtras.Key<T> key, T? t);
+ }
+
+}
+
diff --git a/lifecycle/lifecycle-viewmodel/api/public_plus_experimental_current.txt b/lifecycle/lifecycle-viewmodel/api/public_plus_experimental_current.txt
index b2a64d8..d63c190 100644
--- a/lifecycle/lifecycle-viewmodel/api/public_plus_experimental_current.txt
+++ b/lifecycle/lifecycle-viewmodel/api/public_plus_experimental_current.txt
@@ -41,13 +41,14 @@
}
public static interface ViewModelProvider.Factory {
- method public <T extends androidx.lifecycle.ViewModel> T create(Class<T> modelClass);
+ method public default <T extends androidx.lifecycle.ViewModel> T create(Class<T> modelClass);
+ method public default <T extends androidx.lifecycle.ViewModel> T create(Class<T> modelClass, androidx.lifecycle.viewmodel.CreationExtras extras);
}
public static class ViewModelProvider.NewInstanceFactory implements androidx.lifecycle.ViewModelProvider.Factory {
ctor public ViewModelProvider.NewInstanceFactory();
- method public <T extends androidx.lifecycle.ViewModel> T create(Class<T> modelClass);
field public static final androidx.lifecycle.ViewModelProvider.NewInstanceFactory.Companion Companion;
+ field public static final androidx.lifecycle.viewmodel.CreationExtras.Key<java.lang.String> VIEW_MODEL_KEY;
}
public static final class ViewModelProvider.NewInstanceFactory.Companion {
@@ -77,3 +78,20 @@
}
+package androidx.lifecycle.viewmodel {
+
+ public interface CreationExtras {
+ method public operator <T> T? get(androidx.lifecycle.viewmodel.CreationExtras.Key<T> key);
+ }
+
+ public static interface CreationExtras.Key<T> {
+ }
+
+ public final class MutableCreationExtras implements androidx.lifecycle.viewmodel.CreationExtras {
+ ctor public MutableCreationExtras();
+ method public <T> T? get(androidx.lifecycle.viewmodel.CreationExtras.Key<T> key);
+ method public operator <T> void set(androidx.lifecycle.viewmodel.CreationExtras.Key<T> key, T? t);
+ }
+
+}
+
diff --git a/lifecycle/lifecycle-viewmodel/api/restricted_current.txt b/lifecycle/lifecycle-viewmodel/api/restricted_current.txt
index b2a64d8..d63c190 100644
--- a/lifecycle/lifecycle-viewmodel/api/restricted_current.txt
+++ b/lifecycle/lifecycle-viewmodel/api/restricted_current.txt
@@ -41,13 +41,14 @@
}
public static interface ViewModelProvider.Factory {
- method public <T extends androidx.lifecycle.ViewModel> T create(Class<T> modelClass);
+ method public default <T extends androidx.lifecycle.ViewModel> T create(Class<T> modelClass);
+ method public default <T extends androidx.lifecycle.ViewModel> T create(Class<T> modelClass, androidx.lifecycle.viewmodel.CreationExtras extras);
}
public static class ViewModelProvider.NewInstanceFactory implements androidx.lifecycle.ViewModelProvider.Factory {
ctor public ViewModelProvider.NewInstanceFactory();
- method public <T extends androidx.lifecycle.ViewModel> T create(Class<T> modelClass);
field public static final androidx.lifecycle.ViewModelProvider.NewInstanceFactory.Companion Companion;
+ field public static final androidx.lifecycle.viewmodel.CreationExtras.Key<java.lang.String> VIEW_MODEL_KEY;
}
public static final class ViewModelProvider.NewInstanceFactory.Companion {
@@ -77,3 +78,20 @@
}
+package androidx.lifecycle.viewmodel {
+
+ public interface CreationExtras {
+ method public operator <T> T? get(androidx.lifecycle.viewmodel.CreationExtras.Key<T> key);
+ }
+
+ public static interface CreationExtras.Key<T> {
+ }
+
+ public final class MutableCreationExtras implements androidx.lifecycle.viewmodel.CreationExtras {
+ ctor public MutableCreationExtras();
+ method public <T> T? get(androidx.lifecycle.viewmodel.CreationExtras.Key<T> key);
+ method public operator <T> void set(androidx.lifecycle.viewmodel.CreationExtras.Key<T> key, T? t);
+ }
+
+}
+
diff --git a/lifecycle/lifecycle-viewmodel/build.gradle b/lifecycle/lifecycle-viewmodel/build.gradle
index 575ce58..e48ccd6 100644
--- a/lifecycle/lifecycle-viewmodel/build.gradle
+++ b/lifecycle/lifecycle-viewmodel/build.gradle
@@ -17,6 +17,7 @@
import androidx.build.LibraryGroups
import androidx.build.Publish
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("AndroidXPlugin")
@@ -52,3 +53,11 @@
inceptionYear = "2017"
description = "Android Lifecycle ViewModel"
}
+
+tasks.withType(KotlinCompile).configureEach {
+ kotlinOptions {
+ freeCompilerArgs += [
+ "-Xjvm-default=all",
+ ]
+ }
+}
diff --git a/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/ViewModelProvider.kt b/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/ViewModelProvider.kt
index 82a33cc..f933a72 100644
--- a/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/ViewModelProvider.kt
+++ b/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/ViewModelProvider.kt
@@ -20,6 +20,10 @@
import androidx.annotation.RestrictTo
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.DEFAULT_KEY
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.defaultFactory
+import androidx.lifecycle.viewmodel.CreationExtras.Key
+import androidx.lifecycle.ViewModelProvider.NewInstanceFactory.Companion.VIEW_MODEL_KEY
+import androidx.lifecycle.viewmodel.CreationExtras
+import androidx.lifecycle.viewmodel.MutableCreationExtras
import java.lang.IllegalArgumentException
import java.lang.RuntimeException
import java.lang.UnsupportedOperationException
@@ -46,10 +50,27 @@
/**
* Creates a new instance of the given `Class`.
*
+ * Default implementation throws [UnsupportedOperationException].
+ *
* @param modelClass a `Class` whose instance is requested
* @return a newly created ViewModel
*/
- public fun <T : ViewModel> create(modelClass: Class<T>): T
+ public fun <T : ViewModel> create(modelClass: Class<T>): T {
+ throw UnsupportedOperationException(
+ "Factory.create(String) is unsupported. This Factory requires " +
+ "`CreationExtras` to be passed into `create` method."
+ )
+ }
+
+ /**
+ * Creates a new instance of the given `Class`.
+ *
+ * @param modelClass a `Class` whose instance is requested
+ * @param extras an additional information for this creation request
+ * @return a newly created ViewModel
+ */
+ public fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T =
+ create(modelClass)
}
/**
@@ -83,6 +104,10 @@
modelClass: Class<T>
): T
+ override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
+ return create(extras[VIEW_MODEL_KEY]!!, modelClass)
+ }
+
override fun <T : ViewModel> create(modelClass: Class<T>): T {
throw UnsupportedOperationException(
"create(String, Class<?>) must be called on implementations of KeyedFactory"
@@ -155,7 +180,7 @@
@Suppress("UNCHECKED_CAST")
@MainThread
public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
- var viewModel = store[key]
+ val viewModel = store[key]
if (modelClass.isInstance(viewModel)) {
(factory as? OnRequeryFactory)?.onRequery(viewModel)
return viewModel as T
@@ -165,13 +190,9 @@
// TODO: log a warning.
}
}
- viewModel = if (factory is KeyedFactory) {
- factory.create(key, modelClass)
- } else {
- factory.create(modelClass)
- }
- store.put(key, viewModel)
- return viewModel
+ val extras = MutableCreationExtras()
+ extras[VIEW_MODEL_KEY] = key
+ return factory.create(modelClass, extras).also { store.put(key, it) }
}
/**
@@ -209,6 +230,19 @@
}
return sInstance!!
}
+
+ private object ViewModelKeyImpl : Key<String>
+ /**
+ * A [CreationExtras.Key] to get a key associated with a requested
+ * `ViewModel` from [CreationExtras]
+ *
+ * `ViewModelProvider` automatically puts a key that was passed to
+ * `ViewModelProvider.get(key, MyViewModel::class.java)`
+ * or generated in `ViewModelProvider.get(MyViewModel::class.java)` to the `CreationExtras` that
+ * are passed to [ViewModelProvider.Factory].
+ */
+ @JvmField
+ val VIEW_MODEL_KEY: Key<String> = ViewModelKeyImpl
}
}
diff --git a/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/viewmodel/CreationExtras.kt b/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/viewmodel/CreationExtras.kt
new file mode 100644
index 0000000..ceb94a4
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/viewmodel/CreationExtras.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2021 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
+
+/**
+ * Simple map-like object that passed in [ViewModelProvider.Factory.create]
+ * to provide an additional information to a factory.
+ *
+ * It allows making `Factory` implementations stateless, which makes an injection of factories
+ * easier because don't require all information be available at construction time.
+ */
+public interface CreationExtras {
+ /**
+ * Key for the elements of [CreationExtras]. [T] is a type of an element with this key.
+ */
+ public interface Key<T>
+
+ /**
+ * Returns an element associated with the given [key]
+ */
+ public operator fun <T> get(key: Key<T>): T?
+}
+
+/**
+ * Mutable implementation of [CreationExtras]
+ */
+public class MutableCreationExtras : CreationExtras {
+ private val map = mutableMapOf<CreationExtras.Key<*>, Any?>()
+
+ /**
+ * Associates the given [key] with [t]
+ */
+ public operator fun <T> set(key: CreationExtras.Key<T>, t: T) {
+ map[key] = t
+ }
+
+ public override fun <T> get(key: CreationExtras.Key<T>): T? {
+ @Suppress("UNCHECKED_CAST")
+ return map[key] as T?
+ }
+}
diff --git a/lifecycle/lifecycle-viewmodel/src/test/java/androidx/lifecycle/ViewModelProviderTest.java b/lifecycle/lifecycle-viewmodel/src/test/java/androidx/lifecycle/ViewModelProviderTest.java
index 7e20deb3..349589f 100644
--- a/lifecycle/lifecycle-viewmodel/src/test/java/androidx/lifecycle/ViewModelProviderTest.java
+++ b/lifecycle/lifecycle-viewmodel/src/test/java/androidx/lifecycle/ViewModelProviderTest.java
@@ -16,12 +16,15 @@
package androidx.lifecycle;
+import static androidx.lifecycle.ViewModelProvider.NewInstanceFactory.VIEW_MODEL_KEY;
+
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import androidx.annotation.NonNull;
import androidx.lifecycle.ViewModelProvider.NewInstanceFactory;
+import androidx.lifecycle.viewmodel.CreationExtras;
import org.junit.Assert;
import org.junit.Before;
@@ -40,7 +43,7 @@
}
@Test
- public void twoViewModelsWithSameKey() throws Throwable {
+ public void twoViewModelsWithSameKey() {
String key = "the_key";
ViewModel1 vm1 = mViewModelProvider.get(key, ViewModel1.class);
assertThat(vm1.mCleared, is(false));
@@ -51,7 +54,7 @@
@Test
- public void localViewModel() throws Throwable {
+ public void localViewModel() {
class VM extends ViewModel1 {
}
try {
@@ -72,13 +75,7 @@
@Test
public void testOwnedBy() {
final ViewModelStore store = new ViewModelStore();
- ViewModelStoreOwner owner = new ViewModelStoreOwner() {
- @NonNull
- @Override
- public ViewModelStore getViewModelStore() {
- return store;
- }
- };
+ ViewModelStoreOwner owner = () -> store;
ViewModelProvider provider = new ViewModelProvider(owner, new NewInstanceFactory());
ViewModel1 viewModel = provider.get(ViewModel1.class);
assertThat(viewModel, is(provider.get(ViewModel1.class)));
@@ -98,25 +95,34 @@
@Test
public void testKeyedFactory() {
final ViewModelStore store = new ViewModelStore();
- ViewModelStoreOwner owner = new ViewModelStoreOwner() {
- @NonNull
- @Override
- public ViewModelStore getViewModelStore() {
- return store;
- }
- };
- ViewModelProvider.KeyedFactory keyed = new ViewModelProvider.KeyedFactory() {
+ ViewModelStoreOwner owner = () -> store;
+ ViewModelProvider.Factory explicitlyKeyed = new ViewModelProvider.Factory() {
@SuppressWarnings("unchecked")
@NonNull
@Override
- public <T extends ViewModel> T create(@NonNull String key,
- @NonNull Class<T> modelClass) {
+ public <T extends ViewModel> T create(@NonNull Class<T> modelClass,
+ @NonNull CreationExtras extras) {
+ String key = extras.get(VIEW_MODEL_KEY);
assertThat(key, is("customkey"));
return (T) new ViewModel1();
}
};
- ViewModelProvider provider = new ViewModelProvider(owner, keyed);
+
+ ViewModelProvider provider = new ViewModelProvider(owner, explicitlyKeyed);
provider.get("customkey", ViewModel1.class);
+
+ ViewModelProvider.Factory implicitlyKeyed = new ViewModelProvider.Factory() {
+ @SuppressWarnings("unchecked")
+ @NonNull
+ @Override
+ public <T extends ViewModel> T create(@NonNull Class<T> modelClass,
+ @NonNull CreationExtras extras) {
+ String key = extras.get(VIEW_MODEL_KEY);
+ assertThat(key, is(notNullValue()));
+ return (T) new ViewModel1();
+ }
+ };
+ new ViewModelProvider(owner, implicitlyKeyed).get("customkey", ViewModel1.class);
}
public static class ViewModelStoreOwnerWithFactory implements