我遇到了一个问题,我认为可以追溯到 ViewModel
范围。我在同一个 Activity 下的同一个导航图中有两个 fragment 。第一个 fragment LoginFragment
实例化一个 UserDataViewModel
并将它的一些数据填充到一个名为 UserData
的对象中
第二个 fragment TodayFragment
应该选择 UserDataViewModel
并使用已经填充的属性来做进一步的工作。但是,当 TodayFragment
尝试访问 UserData
对象时,该对象为空。需要强调的是,UserDataViewModel
仍然具有相同的内存地址,但问题是它丢失了对其数据值的引用。
我已经把它去掉了,这样数据只是从 SharedPreferences
中提取一个字符串并使用 GSON 解析它。
登录 fragment :
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()
}
今天 fragment
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
})
}
UserDataViewModel.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)
}
}
}
}
奇怪的地方在于 View 模型中的
getUserData()
方法。当我在这些行上设置断点时:return userData
MutableLiveData<UserData>().apply { loadUserData() }
return@withContext useCase.loadUserData(USER_UNKNOWN)
那些断点都命中了LoginFragment。但是当它进入 TodayFragment 时,它碰到了 View 模型中的
return userData
,然后立即跳回到 TodayFragment 内部的观察者, View 模型内部的值出现空指针异常。我认为这在 View 模型本身被实例化的程度上是有道理的,但为什么 View 模型中的值是空的?
出于调试目的,我对来自用例和存储库的响应进行了硬编码,以确保在
ViewModel
中填充了一个值。来自 LoginFragment 的屏幕截图
来自 TodayFragment 的屏幕截图(扩展 AbstractWorkoutFragment)
请注意
mData
现在如何为空,但 ViewModel 是相同的。最佳答案
在这种情况下,您可以使用 navGraphViewModels() 属性委托(delegate)从 fragment 目标中检索 ViewModel,通过这样做,您可以访问“附加”到您的导航图的 View 模型
所以通常你可以在 LoginFragment
和 TodayFragment
中使用以下内容
private val userDataViewModel: UserDataViewModel by navGraphViewModels(R.id.youNavGraphID)
但是在您的情况下,您在初始化时将参数传递到 View 模型中,因此您还必须传递
ViewModelProvider.Factory
private val sharedViewModel: BookViewModel by navGraphViewModels(R.id.navGraph, ::viewModelProducer)
fun viewModelProducer(): ViewModelProvider.Factory {
return object : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return UserDataViewModelFactory(prefs, dataFetcherService) as T
}
}
}
而不仅仅是
userDataViewModel = activity?.run {
ViewModelProviders
.of(this, UserDataViewModelFactory(prefs, dataFetcherService))
.get(UserDataViewModel::class.java)
} ?: throw Exception("Invalid Activity")
--- 更新 ---
我刚刚面临同样的情况。我会保留我以前的答案,但我相信下面的解决方案应该可以解决您的问题。
当您初始化 Activity/Fragments 范围 View 模型时,您不应在
this
中传递 ViewModelProviders.of()
,因为这意味着 Fragment
本身而不是 Activity 。 userDataViewModel = activity?.run {
ViewModelProviders
.of(this, UserDataViewModelFactory(prefs, dataFetcherService))
.get(UserDataViewModel::class.java)
} ?: throw Exception("Invalid Activity")
相反,您应该按如下方式使用该 Activity 。
userDataViewModel = activity?.run {
ViewModelProviders
.of(it, UserDataViewModelFactory(prefs, dataFetcherService))
.get(UserDataViewModel::class.java)
} ?: throw Exception("Invalid Activity")