Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hilt creating different instances of view model inside same activity

After recently migrating from Dagger to Hilt I started observing very strange behavior with respect to ViewModels. Below is the code snippet:


@HiltAndroidApp
class AndroidApplication : Application() {}

@Singleton
class HomeViewModel @ViewModelInject constructor() :
    ViewModel() {}

@AndroidEntryPoint
class HomeFragment : Fragment(R.layout.fragment_home) {

    private val homeViewModel by viewModels<HomeViewModel>()

    override fun onResume() {
        super.onResume()
        Timber.i("hashCode: ${homeViewModel.hashCode()}")
    }
}


@AndroidEntryPoint
class SomeOtherFragment : Fragment(R.layout.fragment_home) {

    private val homeViewModel by viewModels<HomeViewModel>()

    override fun onResume() {
        super.onResume()
        Timber.i("hashCode: ${homeViewModel.hashCode()}")
    }
}

The value of hashCode isn't consistent in all the fragments. I am unable to figure out what else am I missing for it to generate singleton instance of viewmodel within the activity.

I am using single activity design and have added all the required dependencies.

like image 296
ritesh4302 Avatar asked Jun 24 '20 16:06

ritesh4302


People also ask

How do you inject ViewModel in activity using hilt?

To use @HiltViewModel , you'll need to add these 2 libraries on top of the Dagger Hilt Libraries added as shared in the article above. In your ViewModel, just add @HiltViewModel before the class and the usual @Inject to the constructor. Here we auto have the savedStateHandler injected too.

What is @HiltViewModel?

Annotation Type HiltViewModel Identifies a ViewModel for construction injection. The ViewModel annotated with HiltViewModel will be available for creation by the dagger. hilt. android.


2 Answers

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...

like image 26
EpicPandaForce Avatar answered Sep 21 '22 08:09

EpicPandaForce


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>()
like image 52
ianhanniballake Avatar answered Sep 19 '22 08:09

ianhanniballake