[go: nahoru, domu]

Provide a ViewModelStoreOwner parameter for viewModel()

The current viewModel() always uses the
LocalViewModelStoreOwner.current value as the
ViewModelStoreOwner. This means when using
an alternate ViewModelStoreOwner (such as one
from a parent navigation graph, previousBackStackEntry,
or from the containing activity), developers have
needed to use ViewModelProvider directly.

By providing a parameter of viewModel() that takes
an explicit ViewModelStoreOwner, developers can
use the same syntax.

In cases where the Factory depends on the owner,
developers would get the owner first, build the
factory, then pass both to viewModel:

val owner = OwnerProvider.get()
val factory = Factory(owner)
val viewModel: MyViewModel = viewModel(owner, factory)

Relnote: "Added a new `viewModel()` overload that takes an
explicit `ViewModelStoreOwner`, making it easier to work
with owners other than the `LocalViewModelStoreOwner`."
Test: updated ViewModelTest tests pass
BUG: 188693123

Change-Id: I2628d27791bfeb8a0d2f45b0fa8e9e72cb00c34b
diff --git a/hilt/hilt-navigation-compose/api/current.txt b/hilt/hilt-navigation-compose/api/current.txt
index 447d5c6..ffa3e52 100644
--- a/hilt/hilt-navigation-compose/api/current.txt
+++ b/hilt/hilt-navigation-compose/api/current.txt
@@ -5,8 +5,7 @@
     method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM! hiltNavGraphViewModel();
     method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM! hiltNavGraphViewModel(androidx.navigation.NavBackStackEntry backStackEntry);
     method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM! hiltNavGraphViewModel(androidx.navigation.NavController, String route);
-    method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM! hiltViewModel();
-    method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM! hiltViewModel(androidx.navigation.NavBackStackEntry backStackEntry);
+    method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM! hiltViewModel(optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner);
   }
 
 }
diff --git a/hilt/hilt-navigation-compose/api/public_plus_experimental_current.txt b/hilt/hilt-navigation-compose/api/public_plus_experimental_current.txt
index 447d5c6..ffa3e52 100644
--- a/hilt/hilt-navigation-compose/api/public_plus_experimental_current.txt
+++ b/hilt/hilt-navigation-compose/api/public_plus_experimental_current.txt
@@ -5,8 +5,7 @@
     method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM! hiltNavGraphViewModel();
     method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM! hiltNavGraphViewModel(androidx.navigation.NavBackStackEntry backStackEntry);
     method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM! hiltNavGraphViewModel(androidx.navigation.NavController, String route);
-    method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM! hiltViewModel();
-    method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM! hiltViewModel(androidx.navigation.NavBackStackEntry backStackEntry);
+    method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM! hiltViewModel(optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner);
   }
 
 }
diff --git a/hilt/hilt-navigation-compose/api/restricted_current.txt b/hilt/hilt-navigation-compose/api/restricted_current.txt
index 0b008ea..74cf2f4 100644
--- a/hilt/hilt-navigation-compose/api/restricted_current.txt
+++ b/hilt/hilt-navigation-compose/api/restricted_current.txt
@@ -2,12 +2,11 @@
 package androidx.hilt.navigation.compose {
 
   public final class HiltViewModelKt {
+    method @androidx.compose.runtime.Composable @kotlin.PublishedApi internal static androidx.lifecycle.ViewModelProvider.Factory? createHiltViewModelFactory(androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner);
     method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM! hiltNavGraphViewModel();
     method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM! hiltNavGraphViewModel(androidx.navigation.NavBackStackEntry backStackEntry);
     method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM! hiltNavGraphViewModel(androidx.navigation.NavController, String route);
-    method @androidx.compose.runtime.Composable @kotlin.PublishedApi internal static <VM extends androidx.lifecycle.ViewModel> VM hiltViewModel(kotlin.reflect.KClass<VM> kClass, androidx.navigation.NavBackStackEntry backStackEntry);
-    method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM! hiltViewModel();
-    method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM! hiltViewModel(androidx.navigation.NavBackStackEntry backStackEntry);
+    method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM! hiltViewModel(optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner);
   }
 
 }
diff --git a/hilt/hilt-navigation-compose/src/main/java/androidx/hilt/navigation/compose/HiltViewModel.kt b/hilt/hilt-navigation-compose/src/main/java/androidx/hilt/navigation/compose/HiltViewModel.kt
index 7a1210c..2b5ed13 100644
--- a/hilt/hilt-navigation-compose/src/main/java/androidx/hilt/navigation/compose/HiltViewModel.kt
+++ b/hilt/hilt-navigation-compose/src/main/java/androidx/hilt/navigation/compose/HiltViewModel.kt
@@ -21,11 +21,11 @@
 import androidx.hilt.navigation.HiltViewModelFactory
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.ViewModelStoreOwner
 import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
 import androidx.lifecycle.viewmodel.compose.viewModel
 import androidx.navigation.NavBackStackEntry
 import androidx.navigation.NavController
-import kotlin.reflect.KClass
 
 /**
  * Returns an existing
@@ -37,15 +37,16 @@
  * fragment or an activity.
  *
  * @sample androidx.hilt.navigation.compose.samples.NavComposable
+ * @sample androidx.hilt.navigation.compose.samples.NestedNavComposable
  */
 @Composable
-inline fun <reified VM : ViewModel> hiltViewModel(): VM {
-    val owner = LocalViewModelStoreOwner.current
-    return if (owner is NavBackStackEntry) {
-        hiltViewModel(VM::class, owner)
-    } else {
-        viewModel()
+inline fun <reified VM : ViewModel> hiltViewModel(
+    viewModelStoreOwner: ViewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) {
+        "No ViewModelStoreOwner was provided via LocalViewModelStoreOwner"
     }
+): VM {
+    val factory = createHiltViewModelFactory(viewModelStoreOwner)
+    return viewModel(viewModelStoreOwner, factory = factory)
 }
 
 /**
@@ -71,23 +72,9 @@
  * the [NavController] back stack.
  *
  * @param backStackEntry The entry of a [NavController] back stack.
- *
- * @sample androidx.hilt.navigation.compose.samples.NestedNavComposable
- */
-@Composable
-inline fun <reified VM : ViewModel> hiltViewModel(backStackEntry: NavBackStackEntry) =
-    hiltViewModel(VM::class, backStackEntry)
-
-/**
- * Returns an existing
- * [HiltViewModel](https://dagger.dev/api/latest/dagger/hilt/android/lifecycle/HiltViewModel)
- * -annotated [ViewModel] or creates a new one scoped to the current navigation graph present on
- * the [NavController] back stack.
- *
- * @param backStackEntry The entry of a [NavController] back stack.
  */
 @Deprecated(
-    message = "Use hiltViewModel(NavBackStackEntry) instead.",
+    message = "Use hiltViewModel(ViewModelStoreOwner) instead.",
     replaceWith = ReplaceWith("hiltViewModel(backStackEntry)"),
 )
 @Composable
@@ -103,7 +90,7 @@
  * @param route route of a destination that exists on the [NavController] back stack.
  */
 @Deprecated(
-    message = "Use hiltViewModel(NavBackStackEntry) in combination with " +
+    message = "Use hiltViewModel(ViewModelStoreOwner) in combination with " +
         "NavController#getBackStackEntry(String). This API will be removed in a future version.",
     replaceWith = ReplaceWith("hiltViewModel(this.getBackStackEntry(route))"),
     level = DeprecationLevel.ERROR
@@ -114,13 +101,15 @@
 
 @Composable
 @PublishedApi
-internal fun <VM : ViewModel> hiltViewModel(
-    kClass: KClass<VM>,
-    backStackEntry: NavBackStackEntry
-): VM {
-    val viewModelFactory = HiltViewModelFactory(
+internal fun createHiltViewModelFactory(
+    viewModelStoreOwner: ViewModelStoreOwner
+): ViewModelProvider.Factory? = if (viewModelStoreOwner is NavBackStackEntry) {
+    HiltViewModelFactory(
         context = LocalContext.current,
-        navBackStackEntry = backStackEntry
+        navBackStackEntry = viewModelStoreOwner
     )
-    return ViewModelProvider(backStackEntry, viewModelFactory).get(kClass.java)
+} else {
+    // Use the default factory provided by the ViewModelStoreOwner
+    // and assume it is an @AndroidEntryPoint annotated fragment or activity
+    null
 }
\ No newline at end of file
diff --git a/lifecycle/lifecycle-viewmodel-compose/api/current.txt b/lifecycle/lifecycle-viewmodel-compose/api/current.txt
index 6fe3f20..cf7f13f 100644
--- a/lifecycle/lifecycle-viewmodel-compose/api/current.txt
+++ b/lifecycle/lifecycle-viewmodel-compose/api/current.txt
@@ -9,8 +9,8 @@
   }
 
   public final class ViewModelKt {
-    method @androidx.compose.runtime.Composable public static <T extends androidx.lifecycle.ViewModel> T viewModel(Class<T> modelClass, optional String? key, optional androidx.lifecycle.ViewModelProvider.Factory? factory);
-    method @androidx.compose.runtime.Composable public static inline <reified T extends androidx.lifecycle.ViewModel> T! viewModel(optional String key, optional androidx.lifecycle.ViewModelProvider.Factory? factory);
+    method @androidx.compose.runtime.Composable public static <VM extends androidx.lifecycle.ViewModel> VM viewModel(Class<VM> modelClass, optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String? key, optional androidx.lifecycle.ViewModelProvider.Factory? factory);
+    method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM! viewModel(optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String key, optional androidx.lifecycle.ViewModelProvider.Factory? factory);
   }
 
 }
diff --git a/lifecycle/lifecycle-viewmodel-compose/api/public_plus_experimental_current.txt b/lifecycle/lifecycle-viewmodel-compose/api/public_plus_experimental_current.txt
index 6fe3f20..cf7f13f 100644
--- a/lifecycle/lifecycle-viewmodel-compose/api/public_plus_experimental_current.txt
+++ b/lifecycle/lifecycle-viewmodel-compose/api/public_plus_experimental_current.txt
@@ -9,8 +9,8 @@
   }
 
   public final class ViewModelKt {
-    method @androidx.compose.runtime.Composable public static <T extends androidx.lifecycle.ViewModel> T viewModel(Class<T> modelClass, optional String? key, optional androidx.lifecycle.ViewModelProvider.Factory? factory);
-    method @androidx.compose.runtime.Composable public static inline <reified T extends androidx.lifecycle.ViewModel> T! viewModel(optional String key, optional androidx.lifecycle.ViewModelProvider.Factory? factory);
+    method @androidx.compose.runtime.Composable public static <VM extends androidx.lifecycle.ViewModel> VM viewModel(Class<VM> modelClass, optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String? key, optional androidx.lifecycle.ViewModelProvider.Factory? factory);
+    method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM! viewModel(optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String key, optional androidx.lifecycle.ViewModelProvider.Factory? factory);
   }
 
 }
diff --git a/lifecycle/lifecycle-viewmodel-compose/api/restricted_current.txt b/lifecycle/lifecycle-viewmodel-compose/api/restricted_current.txt
index 6fe3f20..cf7f13f 100644
--- a/lifecycle/lifecycle-viewmodel-compose/api/restricted_current.txt
+++ b/lifecycle/lifecycle-viewmodel-compose/api/restricted_current.txt
@@ -9,8 +9,8 @@
   }
 
   public final class ViewModelKt {
-    method @androidx.compose.runtime.Composable public static <T extends androidx.lifecycle.ViewModel> T viewModel(Class<T> modelClass, optional String? key, optional androidx.lifecycle.ViewModelProvider.Factory? factory);
-    method @androidx.compose.runtime.Composable public static inline <reified T extends androidx.lifecycle.ViewModel> T! viewModel(optional String key, optional androidx.lifecycle.ViewModelProvider.Factory? factory);
+    method @androidx.compose.runtime.Composable public static <VM extends androidx.lifecycle.ViewModel> VM viewModel(Class<VM> modelClass, optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String? key, optional androidx.lifecycle.ViewModelProvider.Factory? factory);
+    method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM! viewModel(optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String key, optional androidx.lifecycle.ViewModelProvider.Factory? factory);
   }
 
 }
diff --git a/lifecycle/lifecycle-viewmodel-compose/src/androidTest/java/androidx/lifecycle/viewmodel/compose/ViewModelTest.kt b/lifecycle/lifecycle-viewmodel-compose/src/androidTest/java/androidx/lifecycle/viewmodel/compose/ViewModelTest.kt
index a35f98b..e4a868d 100644
--- a/lifecycle/lifecycle-viewmodel-compose/src/androidTest/java/androidx/lifecycle/viewmodel/compose/ViewModelTest.kt
+++ b/lifecycle/lifecycle-viewmodel-compose/src/androidTest/java/androidx/lifecycle/viewmodel/compose/ViewModelTest.kt
@@ -81,13 +81,16 @@
     @Test
     public fun viewModelCreatedViaDefaultFactory() {
         val owner = FakeViewModelStoreOwner()
+        var createdInComposition: Any? = null
         rule.setContent {
             CompositionLocalProvider(LocalViewModelStoreOwner provides owner) {
-                viewModel<TestViewModel>()
+                createdInComposition = viewModel<TestViewModel>()
             }
         }
 
         assertThat(owner.factory.createCalled).isTrue()
+        val createdManually = ViewModelProvider(owner).get(TestViewModel::class.java)
+        assertThat(createdInComposition).isEqualTo(createdManually)
     }
 
     @Test
@@ -96,36 +99,6 @@
         var createdInComposition: Any? = null
         rule.setContent {
             CompositionLocalProvider(LocalViewModelStoreOwner provides owner) {
-                createdInComposition = viewModel<TestViewModel>()
-            }
-        }
-
-        assertThat(owner.factory.createCalled).isTrue()
-        val createdManually = ViewModelProvider(owner).get(TestViewModel::class.java)
-        assertThat(createdInComposition).isEqualTo(createdManually)
-    }
-
-    @Test
-    public fun createdViewModelIsEqualsToCreatedManually() {
-        val owner = FakeViewModelStoreOwner()
-        var createdInComposition: Any? = null
-        rule.setContent {
-            CompositionLocalProvider(LocalViewModelStoreOwner provides owner) {
-                createdInComposition = viewModel<TestViewModel>()
-            }
-        }
-
-        assertThat(owner.factory.createCalled).isTrue()
-        val createdManually = ViewModelProvider(owner).get(TestViewModel::class.java)
-        assertThat(createdInComposition).isEqualTo(createdManually)
-    }
-
-    @Test
-    public fun createdViewModelIsEqualsToCreatedManuallyWithKey() {
-        val owner = FakeViewModelStoreOwner()
-        var createdInComposition: Any? = null
-        rule.setContent {
-            CompositionLocalProvider(LocalViewModelStoreOwner provides owner) {
                 createdInComposition =
                     viewModel<TestViewModel>(key = "test")
             }
@@ -137,6 +110,23 @@
     }
 
     @Test
+    public fun viewModelCreatedViaDefaultFactoryWithCustomOwner() {
+        val customOwner = FakeViewModelStoreOwner()
+        val owner = FakeViewModelStoreOwner()
+        var createdInComposition: Any? = null
+        rule.setContent {
+            CompositionLocalProvider(LocalViewModelStoreOwner provides owner) {
+                createdInComposition = viewModel<TestViewModel>(customOwner)
+            }
+        }
+
+        assertThat(owner.factory.createCalled).isFalse()
+        assertThat(customOwner.factory.createCalled).isTrue()
+        val createdManually = ViewModelProvider(customOwner).get(TestViewModel::class.java)
+        assertThat(createdInComposition).isEqualTo(createdManually)
+    }
+
+    @Test
     public fun customFactoryIsUsedWhenProvided() {
         val owner = FakeViewModelStoreOwner()
         val customFactory = FakeViewModelProviderFactory()
@@ -150,6 +140,17 @@
     }
 
     @Test
+    public fun customFactoryProducerIsUsedWhenProvided() {
+        val owner = FakeViewModelStoreOwner()
+        val customFactory = FakeViewModelProviderFactory()
+        rule.setContent {
+            viewModel<TestViewModel>(owner, factory = customFactory)
+        }
+
+        assertThat(customFactory.createCalled).isTrue()
+    }
+
+    @Test
     public fun defaultFactoryIsNotUsedWhenCustomProvided() {
         val owner = FakeViewModelStoreOwner()
         val customFactory = FakeViewModelProviderFactory()
diff --git a/lifecycle/lifecycle-viewmodel-compose/src/main/java/androidx/lifecycle/viewmodel/compose/ViewModel.kt b/lifecycle/lifecycle-viewmodel-compose/src/main/java/androidx/lifecycle/viewmodel/compose/ViewModel.kt
index f6f32e2..70bf232 100644
--- a/lifecycle/lifecycle-viewmodel-compose/src/main/java/androidx/lifecycle/viewmodel/compose/ViewModel.kt
+++ b/lifecycle/lifecycle-viewmodel-compose/src/main/java/androidx/lifecycle/viewmodel/compose/ViewModel.kt
@@ -22,51 +22,60 @@
 import androidx.lifecycle.ViewModelStoreOwner
 
 /**
- * Returns an existing [ViewModel] or creates a new one in the scope (usually, a fragment or
- * an activity)
+ * Returns an existing [ViewModel] or creates a new one in the given owner (usually, a fragment or
+ * an activity), defaulting to the owner provided by [LocalViewModelStoreOwner].
  *
- * The created [ViewModel] is associated with the given scope and will be retained
- * as long as the scope is alive (e.g. if it is an activity, until it is
+ * The created [ViewModel] is associated with the given [viewModelStoreOwner] and will be retained
+ * as long as the owner is alive (e.g. if it is an activity, until it is
  * finished or process is killed).
  *
+ * @param viewModelStoreOwner The owner of the [ViewModel] that controls the scope and lifetime
+ * of the returned [ViewModel]. Defaults to using [LocalViewModelStoreOwner].
  * @param key The key to use to identify the [ViewModel].
- * @return A [ViewModel] that is an instance of the given [T] type.
+ * @param factory The [ViewModelProvider.Factory] that should be used to create the [ViewModel]
+ * or null if you would like to use the default factory from the [LocalViewModelStoreOwner]
+ * @return A [ViewModel] that is an instance of the given [VM] type.
  */
 @Suppress("MissingJvmstatic")
 @Composable
-public inline fun <reified T : ViewModel> viewModel(
+public inline fun <reified VM : ViewModel> viewModel(
+    viewModelStoreOwner: ViewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) {
+        "No ViewModelStoreOwner was provided via LocalViewModelStoreOwner"
+    },
     key: String? = null,
     factory: ViewModelProvider.Factory? = null
-): T = viewModel(T::class.java, key, factory)
+): VM = viewModel(VM::class.java, viewModelStoreOwner, key, factory)
 
 /**
  * Returns an existing [ViewModel] or creates a new one in the scope (usually, a fragment or
  * an activity)
  *
- * The created [ViewModel] is associated with the given scope and will be retained
+ * The created [ViewModel] is associated with the given [viewModelStoreOwner] and will be retained
  * as long as the scope is alive (e.g. if it is an activity, until it is
  * finished or process is killed).
  *
  * @param modelClass The class of the [ViewModel] to create an instance of it if it is not
  * present.
+ * @param viewModelStoreOwner The scope that the created [ViewModel] should be associated with.
  * @param key The key to use to identify the [ViewModel].
- * @return A [ViewModel] that is an instance of the given [T] type.
+ * @return A [ViewModel] that is an instance of the given [VM] type.
  */
 @Suppress("MissingJvmstatic")
 @Composable
-public fun <T : ViewModel> viewModel(
-    modelClass: Class<T>,
+public fun <VM : ViewModel> viewModel(
+    modelClass: Class<VM>,
+    viewModelStoreOwner: ViewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) {
+        "No ViewModelStoreOwner was provided via LocalViewModelStoreOwner"
+    },
     key: String? = null,
     factory: ViewModelProvider.Factory? = null
-): T = checkNotNull(LocalViewModelStoreOwner.current) {
-    "No ViewModelStoreOwner was provided via LocalViewModelStoreOwner"
-}.get(modelClass, key, factory)
+): VM = viewModelStoreOwner.get(modelClass, key, factory)
 
-private fun <T : ViewModel> ViewModelStoreOwner.get(
-    javaClass: Class<T>,
+private fun <VM : ViewModel> ViewModelStoreOwner.get(
+    javaClass: Class<VM>,
     key: String? = null,
     factory: ViewModelProvider.Factory? = null
-): T {
+): VM {
     val provider = if (factory != null) {
         ViewModelProvider(this, factory)
     } else {