查看原文
其他

Hilt 新组件 | ViewModelComponent

Android Android 开发者 2021-08-05

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


  • Hilt 组件层次结构 (Component hierarchy)
    https://developer.android.google.cn/training/dependency-injection/hilt-android#component-hierarchy


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


  • SavedStateHandle
    https://developer.android.google.cn/reference/androidx/lifecycle/SavedStateHandle



将作用域绑定为 ViewModelComponent



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


  • 导航图
    https://developer.android.google.cn/reference/androidx/navigation/NavBackStackEntry


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


因此:

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


使用 @ViewModelScoped



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


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

@ViewModelScoped // 将类型的作用域限定为 ViewModelclass 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 甚至是导航图所控制,您可以根据需要将作用域限定到这些地方,来获得更大的灵活性和更精细的控制粒度。


  • 导航图
    https://developer.android.google.cn/reference/androidx/navigation/NavBackStackEntry


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



 点击屏末 | 阅读原文 | 即刻了解 Hilt 组件层次结构


推荐阅读

如页面未加载,请刷新重试


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存