首页 > 解决方案 > ViewModel 在同一个 Activity 中跨 Fragment 的作用域

问题描述

我有一个问题,我认为可以追溯到ViewModel范围界定。我在同一个活动下的同一个导航图中有两个片段。第一个片段,LoginFragment实例化 aUserDataViewModel并将它的一些数据填充到一个名为的对象中UserData

第二个片段TodayFragment应该拾取UserDataViewModel并使用已经填充的属性来做进一步的工作。但是,当TodayFragment尝试访问该UserData对象时,该对象为空。需要强调的是,UserDataViewModel仍然有相同的内存地址,但问题是它失去了对其数据值的引用。

我已将其剥离,以便数据只是从中提取字符串SharedPreferences并使用 GSON 对其进行解析。

登录片段

private lateinit var userDataViewModel: UserDataViewModel

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    MyApplication.appComponent?.inject(this)
    prefs = PreferenceManager.getDefaultSharedPreferences(context)

    userDataViewModel = activity?.run {
        ViewModelProviders
            .of(this, UserDataViewModelFactory(prefs, dataFetcherService))
            .get(UserDataViewModel::class.java)
    } ?: throw Exception("Invalid Activity")

    userDataViewModel.getUserData()
}

今日片段

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    prefs = PreferenceManager.getDefaultSharedPreferences(activity)

    userDataViewModel = activity?.run {
        ViewModelProviders
            .of(this, UserDataViewModelFactory(prefs, dataFetcherService))
            .get(UserDataViewModel::class.java)
    } ?: throw Exception("Invalid Activity")
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    userDataViewModel
        .getUserData()
        .observe(this, Observer {
            clubId = it.club_id  // <-- this is where it crashes since 'it' is null
        })
}

用户数据视图模型.kt

class UserDataViewModel(
    prefs: SharedPreferences,
    dataFetcherService: DataFetcherService)
    : ViewModel() {

    private val useCase = UserDataUseCase(prefs, dataFetcherService)

    val userData: MutableLiveData<UserData> by lazy {
        MutableLiveData<UserData>().apply { loadUserData() }
    }

    fun getUserData(): LiveData<UserData> {
        return userData
    }

    private fun loadUserData() {
        viewModelScope.launch {
            withContext(Dispatchers.IO) {
                try {
                    return@withContext useCase.loadUserData(USER_UNKNOWN)
                } catch (t: Throwable) {
                    return@withContext null
                }
            }
                ?.let {
                    userData.postValue(it)
                }
        }
    }
}

奇怪的地方在于getUserData()视图模型中的方法。当我在这些行上设置断点时:

return userData
MutableLiveData<UserData>().apply { loadUserData() }
return@withContext useCase.loadUserData(USER_UNKNOWN)

这些断点都命中了 LoginFragment。但是当它进入 TodayFragment 时,它会return userData在视图模型中命中,然后立即跳回到 TodayFragment 内部的观察者,并带有针对视图模型内部值的空指针异常。

我认为这在视图模型本身被实例化的程度上是有道理的,但是为什么视图模型内部的值是 null 呢?

出于调试目的,我对来自用例和存储库的响应进行硬编码,以确保在ViewModel.

LoginFragment 的屏幕截图 在此处输入图像描述

TodayFragment 的屏幕截图(扩展 AbstractWorkoutFragment)
请注意mData现在如何为空,但 ViewModel 是相同的。 在此处输入图像描述

标签: androidandroid-fragmentsandroid-livedataandroid-architecture-componentsandroid-viewmodel

解决方案


在每个片段中,您都将获得ViewModel这种方式的实例:

ViewModelProviders
        .of(this, UserDataViewModelFactory(prefs, dataFetcherService))

注意this,指向哪个Fragment.this。这意味着,作为LifecycleOwner您提供的实例Fragment。因此,每个 Fragments 都会创建自己的新实例UserDataViewModel.

相反,您真正想要的是重用 的相同实例ViewModel,这意味着LifecycleOwner您应该传入托管活动:

ViewModelProviders
        .of(requireActivity(), UserDataViewModelFactory(prefs, dataFetcherService))

这样,您将ViewModel在两者中获得相同的实例Fragments


推荐阅读