The ViewModelComponent is a member of a Hilt Component hierarchy that follows the lifecycle of the ViewModel and can limit the scope of the type to the Component.

Before ViewModelComponent added to the Hilt, ViewModel classes by ActivityRetainedComponent create and injection. So dependencies can be used not only in the ViewModel scoped, or scope is limited to SingletonComponent or ActivityRetainedComponent ViewModel Shared by all instances of types.

If every page of your App is only an Activity, the content will not be a problem, because the situation will be limited to the scope of the type ActivityRetainedComponent means each page ViewModel classes will receive this type of different instances. However, having only one Activity per page is not appropriate for most apps.

In addition, not the default binding SavedStateHandle ActivityRetainedComponent component.

You can now create and inject a ViewModel through a ViewModelComponent that follows the ViewModel lifecycle. Each ViewModel instance holds a different ViewModelComponent instance, and you can use the @ViewModelScoped annotation to limit the scope of the type to that component.

Position of ViewModelComponent in the lite Hilt component hierarchy

ViewModelComponent inherited from ActivityRetainedComponent, so its type limit depends on the upper SingletonComponent and ActivityRetainedComponent. In addition, the ViewModelComponent defaults to a SavedStateHandle associated with the ViewModel.

Bind the scope to ViewModelComponent

By binding the scope to the ViewModelComponent using @ViewModelScoped and injecting it into the ViewModel, you can achieve greater flexibility and finer granularity of control than other components. A ViewModel can save state during configuration changes, and its lifecycle can be controlled by activities, fragments, or even navigation diagrams.

However, since the ActivityComponent and FragmentComponent do not save state during configuration changes, it is still necessary to scope these components in some cases. In addition, FragmentComponent inherits from ActivityComponent, and multiple ViewModelComponents cannot achieve the same behavior.

Therefore:

  • If you need all viewModels to share instances of the same type, use@ActivityRetainedScopedAnnotation.
  • Use if you need to limit the scope of a type to ViewModel to preserve its state when configuration changes, or to make it controlled by a navigation diagram@ViewModelScopedAnnotation.
  • If you need to limit the scope of a type to Activity and do not want to preserve state when configuration changes@ActivityScopedAnnotations, if you need to limit the scope to Fragment and implement the behavior described above@FragmentScopedAnnotation.

Using the @ ViewModelScoped

You can use this annotation to limit the scope of a type to instances of a particular ViewModel. The ViewModel and its dependencies and their dependencies will all be injected into the same instance.

In the following example, LoginViewModel and RegistrationViewModel use the @ViewModelScoped annotated UserInputAuthData type, giving them different states.

@ViewModelScoped // Limit the scope of the type to ViewModel
class UserInputAuthData(
  private val handle: SavedStateHandle // Default binding in ViewModelComponent
) { /* Logical code and cache data */ }

class RegistrationViewModel(
  private val userInputAuthData: UserInputAuthData,
  private val validateUsernameUseCase: ValidateUsernameUseCase,
  private val validatePasswordUseCase: ValidatePasswordUseCase
) : ViewModel() { / *... * / }

class LoginViewModel(
  private val userInputAuthData: UserInputAuthData,
  private val validateUsernameUseCase: ValidateUsernameUseCase,
  private val validatePasswordUseCase: ValidatePasswordUseCase
) : ViewModel() { / *... * / }

class ValidateUsernameUseCase(
  private val userInputAuthData: UserInputAuthData,
  private val repository: UserRepository
) { / *... * / }

class ValidatePasswordUseCase(
  private val userInputAuthData: UserInputAuthData,
  private val repository: UserRepository
) { / *... * / }
Copy the code

Because UserInputAuthData is scoped to ViewModel, RegistrationViewModel and LoginViewModel will get different instances of UserInputAuthData. However, each unscoped UseCase dependency in a ViewModel uses the same UserInputAuthData instance as its ViewModel.

Add a binding to the ViewModelComponent

As with any component, you can add bindings to the ViewModelComponent. If ValidateUsernameUseCase was an interface in the above code snippet, you could tell Hilt which implementation to use by:

@Module
@InstallIn(ViewModelComponent::class)
object UserAuthModule {

  @Provides
  fun provideValidateUsernameUseCase(
    userInputAuthData: UserInputAuthData.// scope is ViewModelComponent
    repository: UserRepository
  ): ValidateUsernameUseCase {
    return ValidateUsernameUseCaseImpl(userInputAuthData, repository)
  }
}
Copy the code

The ViewModelComponent follows the ViewModel lifecycle and can limit the scope of the type to that component. Because the lifecycle of a ViewModel can be controlled by activities, fragments, or even navigation diagrams, you can limit your scope to these as needed for greater flexibility and finer control granularity.

Use @viewModelScoped to limit the scope of the type to ViewModel. Use @activityRetainedScoped to limit the scope so that all viewModels of the same interface share instances of the same type.