我遇到的问题可以追溯到ViewModel
范围界定。我在同一活动下的同一导航图中有两个片段。第一个片段LoginFragment
实例化一个UserDataViewModel
并将其一些数据填充到名为UserData
第二个片段TodayFragment
应该拾取UserDataViewModel
并使用已经填充的属性来做进一步的工作。但是,在TodayFragment
尝试访问UserData
对象时,该对象为null。要强调的是,UserDataViewModel
仍然具有相同的内存地址,但是问题在于它丢失了对其数据值的引用。
我已对此进行了简化,以便数据仅从SharedPreferences
中提取一个字符串并使用GSON对其进行解析。
LoginFragment :
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()
}
TodayFragment
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)
}
}
}
}
奇怪的是在视图模型的getUserData()
方法中。当我在这些行上设置断点时:
return userData
MutableLiveData<UserData>().apply { loadUserData() }
return@withContext useCase.loadUserData(USER_UNKNOWN)
这些断点都在LoginFragment中。但是,当它进入TodayFragment时,它会在视图模型中命中return userData
,然后立即跳回TodayFragment内部的观察者,并且视图模型内部的值具有空指针异常。
我认为在实例化视图模型本身的范围内是有道理的,但是为什么在视图模型中的值 inside 为空?
出于调试目的,我正在对用例和存储库中的响应进行硬编码,以确保在ViewModel
中填充一个值。
TodayFragment的屏幕截图(扩展了AbstractWorkoutFragment)
请注意,mData
现在为空,但ViewModel却是相同的。
答案 0 :(得分:1)
在这种情况下,您可以使用navGraphViewModels()属性委托从片段目标中检索ViewModel,这样就可以访问“附加”到导航图的viewmodel
因此通常您可以在LoginFragment
和TodayFragment
private val userDataViewModel: UserDataViewModel by navGraphViewModels(R.id.youNavGraphID)
但是对于您而言,您是在初始化视图模型时将参数传递给视图模型的,因此您也必须传递一个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")
--- 更新 ---
我只是面临同样的情况。我将保留以前的答案,但我相信以下解决方案可以解决您的问题。
初始化“活动/片段”作用域视图模型时,不应在this
中传递ViewModelProviders.of()
,因为这意味着Fragment
本身而不是活动。
userDataViewModel = activity?.run {
ViewModelProviders
.of(this, UserDataViewModelFactory(prefs, dataFetcherService))
.get(UserDataViewModel::class.java)
} ?: throw Exception("Invalid Activity")
相反,您应该按以下方式使用活动。
userDataViewModel = activity?.run {
ViewModelProviders
.of(it, UserDataViewModelFactory(prefs, dataFetcherService))
.get(UserDataViewModel::class.java)
} ?: throw Exception("Invalid Activity")
答案 1 :(得分:0)
根据 https://developer.android.com/topic/libraries/architecture/viewmodel#lifecycle
ViewModel保留在内存中,直到其生命周期范围永久消失:对于活动而言,它完成时,对而言,对于片段而言,当它脱离时, >
这意味着视图模型的寿命绑定到活动/片段,而不绑定到应用程序。
在某些情况下,ViewModelProviders会重用该视图模型,但是当您从屏幕上的LoginFragment导航到TodayFragment时,由提供者创建并绑定到LoginFragment的ViewModel将会被销毁,然后创建一个新模型并将其绑定到TodayFragment。 >
我认为这就是为什么您获得空引用的原因。
注意: 我无法解释为什么您在新旧地址之间获得相同的内存地址。根据我的经验,如果没有其他操作覆盖释放的内存,则可以重用释放的内存。也许这就是为什么内存地址没有更改的原因。
答案 2 :(得分:0)
在创建ViewModel时,传递活动上下文而不是片段上下文。只要活动存在,ViewModels就会一直存在。
val activityContext = getActivity()
ViewModelProviders.of(activityContext, ...)
答案 3 :(得分:0)
在每个片段中,您都是通过以下方式获取ViewModel
的实例:
ViewModelProviders
.of(this, UserDataViewModelFactory(prefs, dataFetcherService))
请注意this
,它指向Fragment.this
。这意味着,作为LifecycleOwner
,您要提供Fragment
的实例。因此,每个Fragment都会创建自己的UserDataViewModel
实例。
实际上,您真正想要的是重用ViewModel
的相同实例,这意味着作为LifecycleOwner
,您应该传递托管活动:
ViewModelProviders
.of(requireActivity(), UserDataViewModelFactory(prefs, dataFetcherService))
这样,您将在两个ViewModel
中获得Fragments
的相同实例。
答案 4 :(得分:0)
希望我能理解您的问题,这里有一些建议
尝试将您的ViewModel更改为class UserDataViewModel(application: Application) : AndroidViewModel(application)
。从而使实例与应用程序生命周期相关联。
设置clubId可以为it?.apply { setClubId(club_id) }
,以便应用程序在实际数据可用时起作用。
每个片段都应彼此独立,以便您从LoginFragment内使用说view.findNavController().navigate(R.id.action_today_fragment)
进行导航。本质上,您完全依赖该图进行导航。并使用ViewModel来保存数据。
此外,不应从“活动/片段”中传递数据源(例如,您的SharedPref使用似乎不正确)。应该是
Activities/Fragments <--- ViewModel <------ DataSources