在最近从Dagger迁移到Hilt之后,我开始观察到ViewModels非常奇怪的行为。下面是代码片段:
@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()}")
}
}
哈希代码的值在所有片段中都不一致。我无法弄清楚我还缺少什么才能在活动中生成视图模型的单例实例。
我正在使用单个活动设计,并添加了所有必需的依赖项。
当您使用by viewModels
时,您将创建一个作用域为该单个片段的 ViewModel - 这意味着每个片段都将拥有该 ViewModel 类的单独实例。如果您希望将单个 ViewModel 实例的范围限定为整个活动,则需要使用by activityViewModels
private val homeViewModel by activityViewModels<HomeViewModel>()
Ian 说的是正确的,by viewModels
是片段的扩展函数,它将使用 Fragment 作为 ViewModelStoreOwner。
如果需要将其范围限定为活动,可以使用by activityViewModels
。
但是,您通常不需要活动范围的视图模型。它们在单个活动应用程序中实际上是全局的。
要创建活动全局无状态组件,可以使用 Hilt 中的@ActivityRetainedScope
。这些将可用于在活动或片段中创建的视图模型。
要创建有状态的保留组件,您应该依靠 ~~@ViewModelInject
和@Assisted
~~@HiltViewModel
和@Inject constructor
来获取 SavedStateHandle。
在这一点上,您很可能真的想要一个 NavGraph 范围的视图模型,而不是活动范围的视图模型。
要将 SavedStateHandle 获取到片段内的 NavGraph 范围的视图模型中,请使用val vm = androidx.hilt.navigation.fragment.hiltNavGraphViewModels(R.navigation.nav_graph_id)
。
如果您没有使用Hilt,则可以使用= navGraphViewModels
但您可以使用默认的ViewModelProvider.Factory或CreationExtras获取SavedStateHandle。
这是ianhanniballake提到的另一种解决方案。它允许您在片段之间共享视图模型,同时不将其分配给活动,因此您可以避免像EpicPandaForce所说的那样在单个活动中创建全局视图模型。如果您使用的是导航组件,则可以创建要共享视图模型的片段的嵌套导航图(请遵循本指南:嵌套导航图(
在每个片段中:
private val homeViewModel: HomeViewModel
by navGraphViewModels(R.id.nested_graph_id){defaultViewModelProviderFactory}
当您导航出嵌套图形时,视图模型将被删除。当您导航回嵌套图形时,将重新创建它。
正如此处的其他帖子所提到的,使用by activityViewModels<yourClass>()
会将虚拟机的范围限定为整个活动的生命周期,使其成为一个全局范围,包括整个应用程序,如果它是每个人都使用且 Google 推荐的一种活动架构。
干净,最小的解决方案: 如果您使用的是导航图范围视图模型:
替换此内容:
val vm: SomeViewModel by hiltNavGraphViewModels(R.id.nav_vm_id)
与下面:
val vm by activityViewModels<SomeViewModel>()
这允许我将此 VM 用作活动和这些片段之间的共享视图模型。
否则,即使是实时数据观察器也无法工作,因为它会创建彼此独立的新实例和生命周期。