Hilt 在同一活动中创建视图模型的不同实例

问题描述 投票:0回答:5

最近从 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值不一致。我无法弄清楚我还缺少什么来在活动中生成视图模型的单例实例。

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

android dagger dagger-hilt
5个回答
34
投票

当您使用

by viewModels
时,您正在创建一个作用于该单独 Fragment 的 ViewModel - 这意味着每个 Fragment 将拥有该 ViewModel 类的自己的单独实例。如果您希望单个 ViewModel 实例作用于整个 Activity,您需要使用
by activityViewModels

private val homeViewModel by activityViewModels<HomeViewModel>()

9
投票

Ian说的是对的,

by viewModels
是Fragment的扩展函数,它会使用Fragment作为ViewModelStoreOwner。

如果您需要将其范围限定在 Activity 内,则可以使用

by activityViewModels

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

要创建 Activity 全局无状态组件,可以使用 Hilt 中的

@ActivityRetainedScope
。这些将可供您在 Activity 或 Fragment 中创建的 ViewModel 使用。

要创建有状态保留组件,您应该依赖 ~~

@ViewModelInject
@Assisted
~~
@HiltViewModel
@Inject constructor
来获取 SavedStateHandle。

很有可能,此时您真正想要的不是 Activity 范围的 ViewModel,而是 NavGraph 范围的 ViewModel。

要将 SavedStateHandle 获取到 Fragment 内的 NavGraph 范围的 ViewModel 中,请使用

val vm = androidx.hilt.navigation.fragment.hiltNavGraphViewModels(R.navigation.nav_graph_id)

如果您不使用 Hilt,则可以使用

= navGraphViewModels
,但您可以使用默认的 ViewModelProvider.Factory 或 CreationExtras 获取 SavedStateHandle。


3
投票

这是 ianhanniballake 提到的替代解决方案。它允许您在片段之间共享视图模型,而不将其分配给活动,因此您可以避免在单个活动中创建本质上的全局视图模型,如 EpicPandaForce 所说。如果您使用导航组件,则可以为要共享视图模型的片段创建嵌套导航图(遵循本指南:嵌套导航图

在每个片段中:

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

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


0
投票

正如此处其他帖子所提到的,使用

by activityViewModels<yourClass>()
会将 VM 范围限定到整个 Activity 的生命周期,使其有效地成为整个应用程序的全局范围(如果它是每个人都使用且 Google 推荐的一个 Activity 架构)。

干净、最小的解决方案: 如果您使用导航图范围视图模型:

替换这个:

val vm: SomeViewModel by hiltNavGraphViewModels(R.id.nav_vm_id)

如下:

val vm by activityViewModels<SomeViewModel>()

这允许我使用这个虚拟机作为活动和这些片段之间的共享视图模型。

否则,即使实时数据观察者也无法工作,因为它会创建彼此独立的新实例和生命周期。


0
投票

您可以将以下扩展添加到您的代码中,您将能够检索返回堆栈中先前片段的任何 ViewModel 的相同实例。

@MainThread
public inline fun <reified VM : ViewModel> Fragment.backStackViewModels(
    @IdRes destinationId: Int
): Lazy<VM?> = lazy {
    findNavController().currentBackStack.value.lastOrNull { it.destination.id == destinationId }
        ?.let { backStackEntry ->
            ViewModelLazy(
                VM::class,
                { backStackEntry.viewModelStore },
                { HiltViewModelFactory(requireActivity(), backStackEntry) },
                { defaultViewModelCreationExtras }
            ).value
        } ?: run { null }

首次使用 hiltNavGraphViewModels 必须在所有者片段中创建 viewModel,如下所示:

private val mViewModel by hiltNavGraphViewModels<SomeViewModel>(R.id.someDestination)

然后可以使用 backStackViewModels 在任何其他后续片段中检索 viewModel 的相同实例,如下所示:

private val mViewModel by backStackViewModels<SomeViewModel>(R.id.someDestination)

注:

  • 目的地可以是导航图的 ID,也可以是任何导航图中任何片段的目的地 ID。
  • 两个片段中的目标必须相同才能检索缓存的 viewModel 的相同实例。
  • 如果目标不存在于后面的片段的返回堆栈中,则将返回 null。

Github要点链接

© www.soinside.com 2019 - 2024. All rights reserved.