Hilt 在同一个 activity 中创建不同的视图模型实例
Hilt creating different instances of view model inside same activity
最近从 Dagger 迁移到 Hilt 之后,我开始观察到与 ViewModel 相关的非常奇怪的行为。下面是代码片段:
@HiltAndroidApp
class AndroidApplication : Application() {}
@Singleton
class HomeViewModel @ViewModelInject constructor() :
ViewModel() {}
@AndroidEntryPoint
class HomeFragment : Fragment(R.layout.fragment_home) {
private val homeViewModel by viewModels<HomeViewModel>()
override fun onResume() {
super.onResume()
Timber.i("hashCode: ${homeViewModel.hashCode()}")
}
}
@AndroidEntryPoint
class SomeOtherFragment : Fragment(R.layout.fragment_home) {
private val homeViewModel by viewModels<HomeViewModel>()
override fun onResume() {
super.onResume()
Timber.i("hashCode: ${homeViewModel.hashCode()}")
}
}
hashCode的值在所有片段中不一致。我无法弄清楚在 activity.
中生成 viewmodel 的单例实例还缺少什么
我正在使用单个 activity 设计并添加了所有必需的依赖项。
当您使用 by viewModels
时,您正在创建一个作用域为该单个 Fragment 的 ViewModel - 这意味着每个 Fragment 都将拥有该 ViewModel class 的单独实例。如果您想要将单个 ViewModel 实例的范围限定为整个 Activity,您需要使用 by activityViewModels
private val homeViewModel by activityViewModels<HomeViewModel>()
Ian说的对,by viewModels
是Fragment的扩展函数,会把Fragment作为ViewModelStoreOwner
如果您需要将其限定在 Activity 范围内,您可以使用 by activityViewModels
。
但是,您通常不需要 Activity 范围的 ViewModel。它们在单个 Activity 应用程序中实际上是全局的。
要创建 Activity-global 非状态组件,您可以使用 Hilt 中的 @ActivityRetainedScope
。这些将可用于在 Activity 或 Fragment.
中创建的 ViewModel
要创建有状态的保留组件,您应该依靠 @ViewModelInject
和 @Assisted
来获取 SavedStateHandle。
很有可能在这一点上,您真正想要的不是 Activity 范围的 ViewModel,而是 NavGraph 范围的 ViewModel。
要通过 Hilt 的 @Assisted
注释将 SavedStateHandle 放入 Fragment 内的 NavGraph 范围的 ViewModel 中,您可以(编辑:不能)使用:
//@Deprecated
//inline fun <reified T : ViewModel> Fragment.hiltNavGraphViewModels(@IdRes navGraphIdRes: Int) =
//viewModels<T>(
// ownerProducer = { findNavController().getBackStackEntry(navGraphIdRes) },
// factoryProducer = { defaultViewModelProviderFactory }
//)
.
编辑:由于https://github.com/google/dagger/issues/2152上述方法不起作用,所以可以使用访问器并直接使用访问器构建 NavGraph 范围的 AbstractSavedStateViewModelFactory .目前有点乱,因为ActivityRetainedComponent
访问困难,敬请期待更好的解决方案...
这是 ianhanniballake 提到的替代解决方案。它允许您在片段之间共享一个视图模型,而不会将其分配给 activity,因此您可以避免像 EpicPandaForce 所述那样在单个 activity 中创建本质上的全局视图模型。如果您使用的是导航组件,则可以创建要共享视图模型的片段的嵌套导航图(遵循本指南:Nested navigation graphs)
每个片段内:
private val homeViewModel: HomeViewModel
by navGraphViewModels(R.id.nested_graph_id){defaultViewModelProviderFactory}
当您导航出嵌套图时,将删除视图模型。当您导航回嵌套图表时,它将被重新创建。
最近从 Dagger 迁移到 Hilt 之后,我开始观察到与 ViewModel 相关的非常奇怪的行为。下面是代码片段:
@HiltAndroidApp
class AndroidApplication : Application() {}
@Singleton
class HomeViewModel @ViewModelInject constructor() :
ViewModel() {}
@AndroidEntryPoint
class HomeFragment : Fragment(R.layout.fragment_home) {
private val homeViewModel by viewModels<HomeViewModel>()
override fun onResume() {
super.onResume()
Timber.i("hashCode: ${homeViewModel.hashCode()}")
}
}
@AndroidEntryPoint
class SomeOtherFragment : Fragment(R.layout.fragment_home) {
private val homeViewModel by viewModels<HomeViewModel>()
override fun onResume() {
super.onResume()
Timber.i("hashCode: ${homeViewModel.hashCode()}")
}
}
hashCode的值在所有片段中不一致。我无法弄清楚在 activity.
中生成 viewmodel 的单例实例还缺少什么我正在使用单个 activity 设计并添加了所有必需的依赖项。
当您使用 by viewModels
时,您正在创建一个作用域为该单个 Fragment 的 ViewModel - 这意味着每个 Fragment 都将拥有该 ViewModel class 的单独实例。如果您想要将单个 ViewModel 实例的范围限定为整个 Activity,您需要使用 by activityViewModels
private val homeViewModel by activityViewModels<HomeViewModel>()
Ian说的对,by viewModels
是Fragment的扩展函数,会把Fragment作为ViewModelStoreOwner
如果您需要将其限定在 Activity 范围内,您可以使用 by activityViewModels
。
但是,您通常不需要 Activity 范围的 ViewModel。它们在单个 Activity 应用程序中实际上是全局的。
要创建 Activity-global 非状态组件,您可以使用 Hilt 中的 @ActivityRetainedScope
。这些将可用于在 Activity 或 Fragment.
要创建有状态的保留组件,您应该依靠 @ViewModelInject
和 @Assisted
来获取 SavedStateHandle。
很有可能在这一点上,您真正想要的不是 Activity 范围的 ViewModel,而是 NavGraph 范围的 ViewModel。
要通过 Hilt 的 @Assisted
注释将 SavedStateHandle 放入 Fragment 内的 NavGraph 范围的 ViewModel 中,您可以(编辑:不能)使用:
//@Deprecated
//inline fun <reified T : ViewModel> Fragment.hiltNavGraphViewModels(@IdRes navGraphIdRes: Int) =
//viewModels<T>(
// ownerProducer = { findNavController().getBackStackEntry(navGraphIdRes) },
// factoryProducer = { defaultViewModelProviderFactory }
//)
.
编辑:由于https://github.com/google/dagger/issues/2152上述方法不起作用,所以可以使用访问器并直接使用访问器构建 NavGraph 范围的 AbstractSavedStateViewModelFactory .目前有点乱,因为ActivityRetainedComponent
访问困难,敬请期待更好的解决方案...
这是 ianhanniballake 提到的替代解决方案。它允许您在片段之间共享一个视图模型,而不会将其分配给 activity,因此您可以避免像 EpicPandaForce 所述那样在单个 activity 中创建本质上的全局视图模型。如果您使用的是导航组件,则可以创建要共享视图模型的片段的嵌套导航图(遵循本指南:Nested navigation graphs)
每个片段内:
private val homeViewModel: HomeViewModel
by navGraphViewModels(R.id.nested_graph_id){defaultViewModelProviderFactory}
当您导航出嵌套图时,将删除视图模型。当您导航回嵌套图表时,它将被重新创建。