ViewModelComponent
是一个 Hilt 组件层次结构 (Component hierarchy) 中的一员,它遵循 ViewModel 的生命周期,并可以限定类型的作用域到此组件上。
在 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
。
将作用域绑定为 ViewModelComponent
与其他组件相比,通过使用 @ViewModelScoped 将作用域绑定为 ViewModelComponent,并将其注入到 ViewModel 中,可以获得更好的灵活性和更精细的控制粒度。ViewModel 可以在配置更改中保存状态,并且其生命周期可以被 Activity、Fragment,甚至是 导航图 控制。
但是,由于 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 甚至是 导航图 所控制,您可以根据需要将作用域限定到这些地方,来获得更大的灵活性和更精细的控制粒度。
请使用 @ViewModelScoped
将类型的作用域限定为 ViewModel。使用 @ActivityRetainedScoped
限定作用域,使同一界面的所有的 ViewModel 共享同一个类型的实例。
网友评论