8

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

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

4

3 回答 3

24

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

private val homeViewModel by activityViewModels<HomeViewModel>()
于 2020-06-24T17:16:41.213 回答
3

Ian 说的没错,by viewModels就是 Fragment 的扩展功能,它会将 Fragment 用作 ViewModelStoreOwner。

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

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

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

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

在那个时候,你很可能真的想要一个 NavGraph 范围的 ViewModel,而不是 Activity 范围的 ViewModel。

要通过 Hilt 的注释将 SavedStateHandle 放入 Fragment 内的 NavGraph 范围的 ViewModel @Assisted,您可以(编辑:不能)使用:

//@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很难访问,所以请继续关注更好的解决方案......

于 2020-06-24T17:39:37.350 回答
1

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

在每个片段中:

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

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

于 2021-10-10T02:54:05.793 回答