View Models

Note: Examples on this page assume usage of the Gradle plugin. If you are not using the plugin, please read this page for details.

Hilt View Models

A Hilt View Model is a Jetpack ViewModel that is constructor injected by Hilt. To enable injection of a ViewModel by Hilt use the @HiltViewModel annotation:

Java
Kotlin
@HiltViewModel
public final class FooViewModel extends ViewModel {

  @Inject
  FooViewModel(SavedStateHandle handle, Foo foo) {
    // ...
  }
}
@HiltViewModel
class FooViewModel @Inject constructor(
  val handle: SavedStateHandle,
  val foo: Foo
) : ViewModel()

Then an activity or fragments annotated with @AndroidEntryPoint can get the ViewModel instance as normal using ViewModelProvider or the by viewModels() KTX extension:

Java
Kotlin
@AndroidEntryPoint
public final class MyActivity extends AppCompatActivity {

  private FooViewModel fooViewModel;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    fooViewModel = new ViewModelProvider(this).get(FooViewModel.class);
  }
}
@AndroidEntryPoint
class MyActivity : AppCompatActivity() {
  private val fooViewModel: FooViewModel by viewModels()
}

Warning: Even though the view model has an @Inject constructor, it is an error to request it from Dagger directly (for example, via field injection) since that would result in multiple instances. View Models must be retrieved through the ViewModelProvider API. This is checked at compile time by Hilt.

Only dependencies from the ViewModelComponent and its parent components can be provided into the ViewModel.

The ViewModelComponent also comes with two default bindings available:

View Model Scope

All Hilt View Models are provided by the ViewModelComponent which follows the same lifecycle as a ViewModel, i.e. it survives configuration changes. To scope a dependency to a ViewModel use the @ViewModelScoped annotation.

A @ViewModelScoped type will make it so that a single instance of the scoped type is provided across all dependencies injected into the Hilt View Model. Other instances of a ViewModel that requests the scoped instance will receive a different instance. Scoping to the ViewModelComponent allows for flexible and granular scope since View Models surive configuration changes and their lifecycle is controlled by the activity or fragment. If a single instance needs to be shared across various View Models then it should be scoped using either @ActivityRetainedScoped or @Singleton.

For example, we can scope a dependency to be shared within a single ViewModel as such:

Java
Kotlin
@Module
@InstallIn(ViewModelComponent.class)
final class ViewModelMovieModule {
  @Provides
  @ViewModelScoped
  static MovieRepository provideRepo(SavedStateHandle handle) {
      return new MovieRepository(handle.getString("movie-id"));
  }
}

public final class MovieDetailFetcher {
  @Inject MovieDetailFetcher(MovieRepository movieRepo) {
      // ...
  }
}

public final class MoviePosterFetcher {
  @Inject MoviePosterFetcher(MovieRepository movieRepo) {
      // ...
  }
}

@HiltViewModel
public final class MovieViewModel extends ViewModel {
  @Inject
  MovieViewModel(MovieDetailFetcher detailFetcher, MoviePosterFetcher posterFetcher) {
      // Both detailFetcher and posterFetcher will contain the same instance of
      // the MovieRepository.
  }
}
@Module
@InstallIn(ViewModelComponent::class)
internal object ViewModelMovieModule {
  @Provides
  @ViewModelScoped
  fun provideRepo(handle: SavedStateHandle) =
      MovieRepository(handle.getString("movie-id"));
}

class MovieDetailFetcher @Inject constructor(
  val movieRepo: MovieRepository
)

class MoviePosterFetcher @Inject constructor(
  val movieRepo: MovieRepository
)

@HiltViewModel
class MovieViewModel @Inject constructor(
  val detailFetcher: MovieDetailFetcher,
  val posterFetcher: MoviePosterFetcher
) : ViewModel() {
  init {
    // Both detailFetcher and posterFetcher will contain the same instance of
    // the MovieRepository.
  }
}

Assisted Injection

Hilt View Models can also be assisted injected. Compared to using SavedStateHandle, this enables passing data that are not Parcelable to a Hilt View Model easily. To use assisted injection, annotate the view model constructor with @AssistedInject and the assisted parameters with @Assisted, and specify the assisted factory in the @HiltViewModel annotation:

Java
Kotlin
@HiltViewModel(assistedFactory = MovieViewModelFactory.class)
class MovieViewModel {
  @AssistedInject MovieViewModel(@Assisted int movieId) {
    // ...
  }
}
@HiltViewModel(assistedFactory = MovieViewModelFactory::class)
class MovieViewModel @AssistedInject constructor(
  @Assisted val movieId: Int
) : ViewModel {
  // ...
}

Note: Unlike SavedStateHandle, the values passed through assisted parameters to a Hilt View Model do not get saved to disk. They have the same scope as the view model and do not persist after the lifecycle of the view model has ended, e.g. containing activity gets popped off the stack or process death. Consider using normal injection with SavedStateHandle instead or other mechanisms if persistence is needed.

Next, define the assisted factory with an abstract factory method that returns the view model:

Java
Kotlin
@AssistedFactory
interface MovieViewModelFactory {
  MovieViewModel create(int movieId);
}
@AssistedFactory
interface MovieViewModelFactory {
  fun create(movieId: Int): MovieViewModel
}

Note: It is an error to request the assisted factory for view models from Dagger directly since the factory may be used to create view model instances that are not stored correctly. This is checked at compile time by Hilt.

Finally, pass a callback to the helper function HiltViewModelExtensions.withCreationCallback() to create a CreationExtras that can be used with the ViewModelProvider API or other view model functions like by viewModels(). Use the passed in factory to create a view model instance inside the callback:

Java
Kotlin
@AndroidEntryPoint
public final class MyActivity extends AppCompatActivity {

  private int movieId = 1;
  private MovieViewModel movieViewModel;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    movieViewModel = new ViewModelProvider(
      getViewModelStore(),
      getDefaultViewModelProviderFactory(),
      HiltViewModelExtensions.withCreationCallback(
          getDefaultViewModelCreationExtras(),
          (MyViewModel.Factory factory) -> factory.create(movieId)))
      .get(MyInjectedViewModel.class);
  }
}
@AndroidEntryPoint
class MyActivity : AppCompatActivity() {
  private val movieId = 1
  private val movieViewModel by viewModels<MovieViewModel>(
    extrasProducer = {
      defaultViewModelCreationExtras.withCreationCallback<
          MovieViewModelFactory> { factory ->
              factory.create(movieId)
          }
    }
  )
}

Warning: Do not pass any objects that have a smaller lifecycle than the view model (e.g. an Activity, Fragment, or View) or any objects that reference them to the assisted factory as that would be leaking them.

Note: Unlike normal @AssistedInject types, a Hilt View Models, like all View Models, are memoized by the owner. Once a Hilt View Model instance has been created, the callback will be ignored as long as the view model’s lifecycle has not ended. For example, Hilt does not call the callback to create a new view model instance after configuration changes, nor will it update the values of assisted parameters in the existing view model instances.