Hilt 新组件 | ViewModelComponent
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 中的依赖项仅可以使用未限定作用域、或是将作用域限定到 SingletonComponent 或 ActivityRetainedComponent 中,被所有 ViewModel 共享实例的类型。
如果您的 App 每个页面都仅为一个 Activity,上述内容并不会成为问题,因为此情况中将类型的作用域限定为 ActivityRetainedComponent 意味着每个页面的 ViewModel 类都将获得该类型的不同实例。然而,每个页面仅为一个 Activity 并不适用于大多数 App。
此外,ActivityRetainedComponent 组件不会默认绑定 SavedStateHandle。
现在,您可以通过遵循 ViewModel 生命周期的 ViewModelComponent 组件来创建并注入 ViewModel。每一个 ViewModel 实例持有不同的 ViewModelComponent 实例,您可以使用 @ViewModelScoped 注解,将类型的作用域限定到该组件上。
ViewModelComponent 在精简版 Hilt 组件层次结构中的位置
ViewModelComponent 继承自 ActivityRetainedComponent,因此它的类型限定依赖于上层的 SingletonComponent 和 ActivityRetainedComponent。除此之外,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
但是,由于 ActivityComponent 和 FragmentComponent 不会在配置更改中保存状态,所以在某些情况下仍然有必要限定作用域到这些组件。另外,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,RegistrationViewModel 和 LoginViewModel 将获得不同的 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
推荐阅读