Using Hilt’s ViewModelComponent
By: Manuel Vivo
Tags: Dagger 2, AndroidDev, Android App Development, Dependency Injection

Before the ViewModelComponent was added to Hilt, ViewModel classes were created and injected by the ActivityRetainedComponent . Therefore, dependencies of ViewModels could only use unscoped types or types scoped to either the SingletonComponent or ActivityRetainedComponent whose instance is shared by all ViewModels.

The above might not seem like a problem if each screen of your app is an Activity, as scoping a type to ActivityRetainedComponent would mean each ViewModel class would receive a different instance of that type. However, having an activity per screen is not the case for most apps.

Furthermore, the SavedStateHandle type was not available as a default binding in ActivityRetainedComponent .

Now, ViewModels are created and injected by the ViewModelComponent that follows the lifecycle of a ViewModel. Each ViewModel instance has a different instance of ViewModelComponent , and to scope a type to it, use the @ViewModelScoped annotation.

Position of the ViewModelComponent in a simplified Hilt’s component hierarchy

The ViewModelComponent extends from ActivityRetainedComponent . Therefore, types scoped to ViewModelComponent can depend on types scoped to the ActivityRetainedComponent and SingletonComponent . In addition to this, the ViewModelComponent contains a default binding of the SavedStateHandle associated with its ViewModel.

Scoping to the ViewModelComponent

Scoping to the ViewModelComponent , and therefore to a ViewModel, using @ViewModelScoped gives you more flexibility and granularity than what’s possible with other components. ViewModels survive configuration changes and its lifecycle can be controlled by an activity, fragment, or even a navigation graph!

Still, scoping to ActivityComponent and FragmentComponent is useful since these components don’t survive configuration changes which might be necessary in some scenarios. Also, the FragmentComponent extends the ActivityComponent , behavior that you cannot have with multiple ViewModelComponent s.

Therefore:

To make all ViewModels share the same instance of a type, annotate it with @ActivityRetainedScoped.

To scope a type to a ViewModel, make it survive configuration changes, and/or be controlled by a navigation graph, annotate it with @ViewModelScoped .

. To scope a type to the Activity and don’t make it survive configuration changes, annotate with @ActivityScoped , or @FragmentScoped if you want that behavior but scoping to a fragment.

Using @ViewModelScoped

This annotation allows you to scope a type to an instance of a ViewModel. The same instance will be injected in all the dependencies that the ViewModel and its dependencies have.

In the following example, LoginViewModel and RegistrationViewModel make use of a @ViewModelScoped UserInputAuthData type that contains mutable state.

Since UserInputAuthData is scoped to the ViewModel, RegistrationViewModel and LoginViewModel will receive a different instance of UserInputAuthData . However, the unscoped UseCase dependencies of each ViewModel use the same instance that its ViewModel uses.

Adding bindings to the ViewModelComponent