After recently migrating from Dagger to Hilt I started observing very strange behavior with respect to ViewModels. Below is the code snippet:
@HiltAndroidApp
When you use by viewModels
, you are creating a ViewModel scoped to that individual Fragment - this means each Fragment will have its own individual instance of that ViewModel class. If you want a single ViewModel instance scoped to the entire Activity, you'd want to use by activityViewModels
private val homeViewModel by activityViewModels<HomeViewModel>()
What Ian says is correct, by viewModels
is the Fragment's extension function, and it will use the Fragment as the ViewModelStoreOwner.
If you need it to be scoped to the Activity, you can use by activityViewModels
.
However, you typically don't want Activity-scoped ViewModels. They are effectively global in a single-Activity application.
To create an Activity-global non-stateful component, you can use the @ActivityRetainedScope
in Hilt. These will be available to your ViewModels created in Activity or Fragment.
To create stateful retained components, you should rely on @ViewModelInject
, and @Assisted
to get a SavedStateHandle.
There is a high likelihood that at that point, instead of an Activity-scoped ViewModel, you really wanted a NavGraph-scoped ViewModel.
To get a SavedStateHandle into a NavGraph-scoped ViewModel inside a Fragment via Hilt's @Assisted
annotation, you can (EDIT: can't) use:
//@Deprecated
//inline fun <reified T : ViewModel> Fragment.hiltNavGraphViewModels(@IdRes navGraphIdRes: Int) =
//viewModels<T>(
// ownerProducer = { findNavController().getBackStackEntry(navGraphIdRes) },
// factoryProducer = { defaultViewModelProviderFactory }
//)
.
EDIT: Due to https://github.com/google/dagger/issues/2152 this above approach doesn't work, so what can work is to use accessors and building the NavGraph-scoped AbstractSavedStateViewModelFactory directly with accessors. It's a bit messy at the moment because ActivityRetainedComponent
is hard to access, so stay tuned for a better solution...