我遇到了一个访问viewmodel的问题。
我有一个活动和2个片段。我有一个活动和片段的视图模型,使用在宿主活动中创建的视图模型的相同实例。
class MyViewModel(var paymentDataModel: PaymentDataModel) : ViewModel(){
fun someMethod():Boolean{
//return Something
}
}
class MyViewModelFactory(var paymentDataModel: PaymentDataModel) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return MyViewModel(paymentDataModel) as T
}
}
class NewPaymentAmountFragment : Fragment() {
private val paymentViewModel: MyViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if(paymentViewModel.someMehtod()){
//Accessing activity viewmodel in fragment
}
}
}
如果我在活动函数中使用viewmodel扩展定义viewmodel,它会显示以下错误。
原因:java.lang.RuntimeException: Cannot create an instance of类com.app.MyViewModel
class MyActivity : BaseActivity(){
val myViewModel: MyViewModel by viewModels {
MyViewModelFactory(constructPaymentDataModel()) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
}
但是如果我使用ViewModelProvider()以正常的方式定义ViewModel,它的工作
class MyActivity : BaseActivity(){
lateint var myViewModel: MyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val viewModelFactory = MyViewModelFactory(constructPaymentDataModel())
myViewModel = ViewModelProvider(this, viewModelFactory)[MyViewModel::class.java]
}
}
也只有当viewmodel in fragment被首先访问时才会发生这种情况。
如果我在oncreate活动之前访问过活动中的viewmodel,在片段中它的工作很好。它能够获得视图模型实例。
class MyActivity : BaseActivity(){
val myViewModel: MyViewModel by viewModels {
MyViewModelFactory(constructPaymentDataModel()) }
override fun onCreate(savedInstanceState: Bundle?) {
println(myViewModel.isPaymentMethodExists.value)
super.onCreate(savedInstanceState)
}
}
在这里,我在片段访问活动视图模型之前访问视图模型。所以当断点到达这个println方法时,视图模型是由lazy分配的。
如果我先访问片段中的viewmodel,也是一样的。活动中的惰性视图模型没有被分配。
这是总结,如果viewmodel使用viewmodel扩展在activity和fragment中都定义了,并且viewmodel首先在fragment中被访问,它就不能工作了。
当您访问Fragment
中的ViewModel
时,您没有通过工厂:
private val paymentViewModel: MyViewModel by activityViewModels()
你需要做同样的在Activity
-他们都需要能够构建虚拟机,如果有必要,所以它是相同的代码:
// still using the activityViewModels delegate, because you want the Activity's VM instance
private val paymentViewModel: MyViewModel by activityViewModels {
MyViewModelFactory(constructPaymentDataModel())
}
ViewModel
s的工作方式是每个Activity
和Fragment
类(也是backstack条目,如果你使用Navigation库)可以每个有自己的单个VM实例。这允许同一个Activity
或其他对象的不同实例在销毁后抓取相同的VM对象- VM比它们更长寿,并且它们共享它。
它还允许其他组件获取属于Activity
(或其他)的特定实例。这就是Fragments如何获取父Activity的特定VM副本并彼此共享数据,因为它们都在查看相同的实例。
要获得虚拟机,您调用ViewModelProvider(owner)
(其中owner
是拥有实例的Activity
或Fragment
),然后调用get(SomeViewModel::class.java)
来说明您想要获取哪种类型的虚拟机。如果已经有一个与这个所有者关联的VM实例,它将返回它-这就是所有东西共享同一个VM对象的方式。
(by viewModels
和by activityViewModels
只是很好的速记-它们只是分别调用ViewModelProvider(this)
或viewModelProvider(parentActivity)
,以通过相关所有者并获得所需的VM)
如果没有此VM的实例,则提供者将创建一个并存储它以供其他任何请求的对象使用。这是关键部分-为特定所有者请求VM实例的第一件事是导致它被创建的原因。如果该创建需要工厂(就像您的情况一样),则第一个请求需要提供该工厂.
因此,当您的Activity
是第一个请求VM时,它提供用于创建VM的工厂。然后,当Fragment
稍后发出请求时,它不提供工厂功能并不重要- VM实例已经被存储。但是反过来做的话,你就会遇到问题。因为Fragment
发出请求,并且不提供工厂,所以它尝试使用默认的无参数工厂。这对于具有PaymentDataModel
参数的VM类不起作用,因此您会得到错误。在请求VM的任何地方提供工厂,它就会工作。
希望能澄清!
我想展示一个对我有效的解决方案,也许可以帮助其他面临同样问题的朋友。在我的项目中,我使用的是Hilt。在将@AndroidEntryPoint添加到我调用ViewModel的活动后,问题为我解决了。