Hilt 新组件 | ViewModelComponent

Hilt 新组件 | ViewModelComponent

ViewModelComponent 是一个 Hilt 组件层次结构 (Component hierarchy) 中的一员,它遵循 ViewModel 的生命周期,并可以限定类型的作用域到此组件上。

ViewModelComponent 添加到 Hilt 之前,ViewModel 类通过 ActivityRetainedComponent创建和注入。因此,ViewModel 中的依赖项仅可以使用未限定作用域、或是将作用域限定到 SingletonComponentActivityRetainedComponent 中,被所有 ViewModel 共享实例的类型。

如果您的 App 每个页面都仅为一个 Activity,上述内容并不会成为问题,因为此情况中将类型的作用域限定为 ActivityRetainedComponent 意味着每个页面的 ViewModel 类都将获得该类型的不同实例。然而,每个页面仅为一个 Activity 并不适用于大多数 App。

此外,ActivityRetainedComponent 组件不会默认绑定 SavedStateHandle

现在,您可以通过遵循 ViewModel 生命周期的 ViewModelComponent 组件来创建并注入 ViewModel。每一个 ViewModel 实例持有不同的 ViewModelComponent 实例,您可以使用 @ViewModelScoped 注解,将类型的作用域限定到该组件上。

ViewModelComponent 在精简版 Hilt 组件层次结构中的位置

ViewModelComponent 继承自 ActivityRetainedComponent,因此它的类型限定依赖于上层的 SingletonComponentActivityRetainedComponent。除此之外,ViewModelComponent 还默认绑定了一个与 ViewModel 关联的 SavedStateHandle

将作用域绑定为 ViewModelComponent

与其他组件相比,通过使用 @ViewModelScoped 将作用域绑定为 ViewModelComponent,并将其注入到 ViewModel 中,可以获得更好的灵活性和更精细的控制粒度。ViewModel 可以在配置更改中保存状态,并且其生命周期可以被 Activity、Fragment,甚至是 导航图 控制。

但是,由于 ActivityComponentFragmentComponent 不会在配置更改中保存状态,所以在某些情况下仍然有必要限定作用域到这些组件。另外,FragmentComponent 继承自 ActivityComponent,使用多个 ViewModelComponent 无法实现相同的行为。

因此:

  • 如果需要所有的 ViewModel 共享同一个类型的实例,使用 @ActivityRetainedScoped 注解。
  • 如果需要将类型的作用域限定为 ViewModel,使其在配置更改时保留状态,或使其受导航图控制,使用 @ViewModelScoped 注解。
  • 如果需要将类型的作用域限定为 Activity,并且不希望在配置更改时保留状态,使用 @ActivityScoped 注解,如果需要将作用域限定为 Fragment 并实现上述行为,使用 @FragmentScoped 注解。

使用 @ViewModelScoped

您可以使用该注解将一个类型的作用域限定为特定 ViewModel 的实例。ViewModel 及其依赖项以及他们的依赖都将注入相同的实例。

下面的示例中,LoginViewModel 以及 RegistrationViewModel 分别使用了被 @ViewModelScoped 注解的 UserInputAuthData 类型,使它们拥有不同的状态。

@ViewModelScoped // 将类型的作用域限定为 ViewModel
class UserInputAuthData(
  private val handle: SavedStateHandle //在 ViewModelComponent 中默认绑定
) { /* 逻辑代码以及缓存数据*/ }

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
) { /* ... */ }

因为 UserInputAuthData 的作用域被限定为 ViewModel,RegistrationViewModelLoginViewModel 将获得不同的 UserInputAuthData 实例。然而,每个 ViewModel 中没有限定作用域的 UseCase 依赖会与其 ViewModel 使用相同的 UserInputAuthData 实例。

向 ViewModelComponent 中添加绑定

和其他组件一样,您可以向 ViewModelComponent 中添加绑定。如果在上述代码片段中,ValidateUsernameUseCase 是一个接口,您可以这样通知 Hilt 使用哪种实现:

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

  @Provides
  fun provideValidateUsernameUseCase(
    userInputAuthData: UserInputAuthData,  //作用域为 ViewModelComponent
    repository: UserRepository
  ): ValidateUsernameUseCase {
    return ValidateUsernameUseCaseImpl(userInputAuthData, repository)
  }
}

ViewModelComponent 遵循 ViewModel 的生命周期,并可以将类型的作用域限定到此组件上。由于 ViewModel 的生命周期可以被 Activity、Fragment 甚至是 导航图 所控制,您可以根据需要将作用域限定到这些地方,来获得更大的灵活性和更精细的控制粒度。

请使用 @ViewModelScoped 将类型的作用域限定为 ViewModel。使用 @ActivityRetainedScoped 限定作用域,使同一界面的所有的 ViewModel 共享同一个类型的实例。

版权声明

禁止一切形式的转载-禁止商用-禁止衍生 申请授权

脉脉不得语
脉脉不得语
Zhengzhou Website
Android Developer | https://androiddevtools.cn and https://androidweekly.io WebMaster | GDG Zhengzhou Funder & Ex Organizer | http://Toast.show(∞) Podcast Host

你已经成功订阅到 Android 开发技术周报
太棒了!接下来,完成检验以获得全部访问权限 Android 开发技术周报
欢迎回来!你已经成功登录了。
Unable to sign you in. Please try again.
成功!您的帐户已完全激活,您现在可以访问所有内容。
Error! Stripe checkout failed.
Success! Your billing info is updated.
Error! Billing info update failed.