【问题标题】:Hilt creating different instances of view model inside same activityHilt 在同一活动中创建不同的视图模型实例
【发布时间】:2020-06-24 16:42:15
【问题描述】:

在最近从 Dagger 迁移到 Hilt 之后,我开始观察到关于 ViewModel 的非常奇怪的行为。下面是代码sn-p:


@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 的值在所有片段中并不一致。我无法弄清楚我还缺少什么以在活动中生成视图模型的单例实例。

我正在使用单活动设计并添加了所有必需的依赖项。

【问题讨论】:

  • 不要使用 @Singleton 注释您的 ViewModel。
  • 为什么要使用 @Singleton 注释视图模型?
  • 是的,我已经删除了。

标签: android dagger dagger-hilt


【解决方案1】:

当您使用by viewModels 时,您正在创建一个作用域为该单个 Fragment 的 ViewModel - 这意味着每个 Fragment 将具有其自己的该 ViewModel 类的单个实例。如果您希望将单个 ViewModel 实例限定为整个 Activity,则需要使用 by activityViewModels

private val homeViewModel by activityViewModels<HomeViewModel>()

【讨论】:

  • 它有效,val actViewModel: by activityViewModels()
【解决方案2】:

Ian 说的没错,by viewModels 是 Fragment 的扩展函数,它会将 Fragment 用作 ViewModelStoreOwner。

如果您需要将其限定为 Activity,您可以使用by activityViewModels

但是,您通常不需要 Activity 范围的 ViewModel。它们在单个 Activity 应用程序中实际上是全局的。

要创建一个 Activity 全局的无状态组件,可以使用 Hilt 中的@ActivityRetainedScope。这些将可用于您在 Activity 或 Fragment 中创建的 ViewModel。

要创建有状态的保留组件,您应该依赖 @ViewModelInject@Assisted 来获取 SavedStateHandle。

很有可能在那个时候,您真的想要一个 NavGraph 范围的 ViewModel,而不是 Activity 范围的 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很难访问,敬请期待更好的解决方案...

【讨论】:

  • 请注意,@ActivityRetainedScope 对象存储在活动范围的 ViewModel 中,因此该对象的生命周期与活动范围的 ViewModel 本身的生命周期完全相同。
  • 这是真的,这就是为什么我说它通常应该是无状态的,或者它的状态应该是暂时的(因为你看不到属于的SavedStateHandle封闭的SavedStateHandle)。那里的想法是,如果您使用 Activity 范围的 VM 并且您不需要 SavedStateHandle,那么您可以使用 Hilt 而不 要求 VM 作为持有者(或扩展 @987654333 @)。
【解决方案3】:

这是 ianhanniballake 提到的另一种解决方案。它允许您在片段之间共享视图模型而不将其分配给活动,因此您可以避免在单个活动中创建全局视图模型,如 EpicPandaForce 所述。如果您使用的是 Navigation 组件,则可以创建要共享视图模型的片段的嵌套导航图(按照本指南:Nested navigation graphs

在每个片段内:

private val homeViewModel: HomeViewModel
    by navGraphViewModels(R.id.nested_graph_id){defaultViewModelProviderFactory}

当您离开嵌套图时,视图模型将被删除。当您导航回嵌套图表时,它将重新创建。

【讨论】:

  • 我喜欢这种方法。
猜你喜欢
  • 1970-01-01
  • 2020-10-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-07
相关资源
最近更新 更多