Is there currently a way to use a `viewModel<Vi...
# compose
j
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
Does the extension method at
androidx.compose.ui.viewinterop.viewModel
not do the job for you?
j
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
Weird. Are you using AndroidX's Hilt extensions? I use ViewModelInject annotation from androidx.hilt in mine.
j
Yes
m
I'm out of ideas then, sorry.
j
Application with
@HiltAndroidApp
, ViewModel with
@ViewModelInject
, Activity with
@AndroidEntryPoint
And the ofc the modules and install in
i
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
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
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
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
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
Alright thanks!
i
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
It doesn't use SavedStateHandle
i
Okay, was worth a shot 😅
j
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
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
passing down viewmodels and creating the viewmodels all at once seems... inefficient, but I guess its a solution to the 100% crashing rate.
i
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
True
Well thanks for your help, have a good night:)
i
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
i am facing the same issue but my app crashes directly when navigation to the second screen. is there any workaround?
j
passing down the viewmodel from outside of the NavHost composable to the "composables" inside the navhost
b
but then i get the same viewmodel in every composable. but i want a new viewmodel scoped to the composable
j
i do not think the intended way is any different? Viewmodels should stay in memory forever
b
@Ian Lake can you Help. I think the intended behavior should be that the viewmodel is scoped to the composable?
b
@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
i
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
@SaurabhS can you please explain your workaround? Do I need to create factory for every viewmodel?