https://kotlinlang.org logo
#compose
Title
# compose
j

jaqxues

11/01/2020, 2:45 PM
Is there currently a way to use a 
viewModel<ViewModelClass>()
 function to get a ViewModel in Compose with Hilt/Dagger? Something that i can use like  this
Copy code
@Composable
inline fun <reified T: ViewModel> viewModel() =
    (ContextAmbient.current as MainActivity).viewModels<T>()

@Composable
fun Example() {
    val viewModel by viewModel<SomeViewModel>()
    viewModel.doStuff()
}
The above works, but it certainly is not elegant or safe or anything
m

msfjarvis

11/01/2020, 3:14 PM
Does the extension method at
androidx.compose.ui.viewinterop.viewModel
not do the job for you?
j

jaqxues

11/01/2020, 3:16 PM
Copy code
java.lang.RuntimeException: Cannot create an instance of class com.jaqxues.example.viewmodel.SomeViewModel
        at androidx.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.java:221)
        at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.java:278)
        at androidx.lifecycle.SavedStateViewModelFactory.create(SavedStateViewModelFactory.java:112)
        at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:185)
        at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:150)
        at androidx.compose.ui.viewinterop.ViewModelKt.get(ViewModel.kt:75)
        at androidx.compose.ui.viewinterop.ViewModelKt.viewModel(ViewModel.kt:60)
m

msfjarvis

11/01/2020, 3:20 PM
Weird. Are you using AndroidX's Hilt extensions? I use ViewModelInject annotation from androidx.hilt in mine.
j

jaqxues

11/01/2020, 3:20 PM
Yes
m

msfjarvis

11/01/2020, 3:20 PM
I'm out of ideas then, sorry.
j

jaqxues

11/01/2020, 3:21 PM
Application with
@HiltAndroidApp
, ViewModel with
@ViewModelInject
, Activity with
@AndroidEntryPoint
And the ofc the modules and install in
i

Ian Lake

11/01/2020, 3:31 PM
It sounds like you're using
viewModel()
inside a
NavHost
, which is where you'd need to follow the Hilt docs on using ViewModels with Navigation: https://kotlinlang.slack.com/archives/CJLTWPH7S/p1604071670473700?thread_ts=1604043017.440100&amp;cid=CJLTWPH7S
j

jaqxues

11/01/2020, 3:51 PM
Sorry, I don't really understand Do I need to pass the
defaultViewModelProviderFactory
every time to get a viewModel or is there an easier way? And the
navGraphViewModels
is defined for a Fragment, not for a Composable.
i

Ian Lake

11/01/2020, 4:00 PM
Every
viewModel()
instance within a
NavHost
is creating a ViewModel scoped to your navigation destination, exactly like
navGraphViewModels
does. So yes, if you want to use a
defaultViewModelProviderFactory
that is tied to your Hilt enabled activity/fragment, you need to specifically say that
j

jaqxues

11/01/2020, 4:22 PM
Alright thanks. Wish there was a function and an Ambient or something pre-done, I imagine a lot of people will be in this situation once the transition to navigation-compose and Compose itself is Mainstream.
I give up, getting an error when navigating back and re-opening a screen. Waiting for a proper example app
Copy code
java.lang.IllegalArgumentException: SavedStateProvider with the given key is already registered
        at androidx.savedstate.SavedStateRegistry.registerSavedStateProvider(SavedStateRegistry.java:111)
        at androidx.lifecycle.SavedStateHandleController.attachToLifecycle(SavedStateHandleController.java:50)
        at androidx.lifecycle.SavedStateHandleController.create(SavedStateHandleController.java:70)
        at androidx.lifecycle.AbstractSavedStateViewModelFactory.create(AbstractSavedStateViewModelFactory.java:67)
        at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:185)
        at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:150)
        at androidx.compose.ui.viewinterop.ViewModelKt.get(ViewModel.kt:75)
        at androidx.compose.ui.viewinterop.ViewModelKt.viewModel(ViewModel.kt:60)
i

Ian Lake

11/01/2020, 6:28 PM
Yeah, Dagger/Hilt being tied solely to the activity / fragment layer means that there's a lot more work to be done for any custom scoping, whether it is for Navigation (either in the old world or Compose world) and just for scoping at the individual Composable level. When I chatted with them (actually just this last week), they're definitely thinking hard about making a really good experience for Compose end to end
j

jaqxues

11/01/2020, 6:30 PM
Alright thanks!
i

Ian Lake

11/01/2020, 6:30 PM
That issue looks like related to another issue I saw on Hilt's tracker: https://github.com/google/dagger/issues/2152 - is your Hilt injected ViewModel also using SavedStateHandle?
You might be able to avoid that exception and other issues if you don't use SavedStateHandle
j

jaqxues

11/01/2020, 6:36 PM
It doesn't use SavedStateHandle
i

Ian Lake

11/01/2020, 6:38 PM
Okay, was worth a shot 😅
j

jaqxues

11/01/2020, 6:38 PM
I'll just use the Ambient thing I wrote now and wait for something better that does not crash when trying to navigate back. ¯\_(ツ)_/¯
i

Ian Lake

11/01/2020, 8:05 PM
I think the problem you're facing is the opposite of that issue I linked, but the same root issue: the
defaultViewModelProviderFactory
stores it's saved state at the activity/fragment layer. In that issue I linked, the fragment is within the Navigation graph, so the issue is that the saved state is too small of a scope (the navigation graph encompasses multiple fragments). In your Compose case, the entire navigation graph in within the single activity/fragment, so there the scope is too large and you end up saving state multiple times with the same key.
You can, of course, create your ViewModel outside of the
NavHost
, which only happens to work because then your scopes match - it is scoped to the entire Compose hierarchy which is the same as the activity/fragment that hosts it
j

jaqxues

11/01/2020, 8:08 PM
passing down viewmodels and creating the viewmodels all at once seems... inefficient, but I guess its a solution to the 100% crashing rate.
i

Ian Lake

11/01/2020, 8:12 PM
Keep in mind that all ViewModels made by composables in the non-Navigation using world are all created at the same activity/fragment layer, so every ViewModel is going to stay in memory for the lifetime of your process, even if the creating Composable is no longer in your hierarchy
j

jaqxues

11/01/2020, 8:13 PM
True
Well thanks for your help, have a good night:)
i

Ian Lake

11/01/2020, 8:14 PM
I'd also suggest filing an issue on the dagger issue tracker, so that they're aware of this inverse problem with the saved state
(like I said, I suspect the fix to that other issue would fix that one as well)
b

bodo

11/06/2020, 2:22 PM
i am facing the same issue but my app crashes directly when navigation to the second screen. is there any workaround?
j

jaqxues

11/06/2020, 2:23 PM
passing down the viewmodel from outside of the NavHost composable to the "composables" inside the navhost
b

bodo

11/06/2020, 2:27 PM
but then i get the same viewmodel in every composable. but i want a new viewmodel scoped to the composable
j

jaqxues

11/06/2020, 2:50 PM
i do not think the intended way is any different? Viewmodels should stay in memory forever
b

bodo

11/06/2020, 3:10 PM
@Ian Lake can you Help. I think the intended behavior should be that the viewmodel is scoped to the composable?
b

bodo

11/06/2020, 3:48 PM
@jaqxues thats true for composables NOT using the composable navigation. But when the composables are defined in a NavHost the viewmodels should be scoped to the composables
s

SaurabhS

11/06/2020, 4:00 PM
i

Ian Lake

11/06/2020, 4:26 PM
You can't have both Hilt enabled ViewModels and scoping to a destination in your NavHost. It is a case of picking one or the other until Hilt can fix the scoping issues mentioned above
z

zoha131

12/20/2020, 7:31 PM
@SaurabhS can you please explain your workaround? Do I need to create factory for every viewmodel?
31 Views