如果使用setVariable()方法设置ViewModel,则DataBinding无法正常工作
我有ParentFragment
和ChildFragment
.我正在使用Koin进行DI.
I have ParentFragment
and ChildFragment
. I am using Koin for DI.
在一种情况下,数据绑定无效,而在另一种情况下,数据绑定有效.
In one case, data binding is not working and in another it is working.
不起作用的情况: ParentFragment
abstract class ParentFragment<T: ViewDataBinding, V: ParentViewModel>: Fragment() {
@LayoutRes
abstract fun getLayoutResId(): Int
abstract fun init()
protected lateinit var binding: T
protected abstract val mViewModel: V
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
return DataBindingUtil.inflate<T>(inflater, getLayoutResId(), container, false).apply { binding = this }.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
doDataBinding()
}
private fun doDataBinding() {
binding.lifecycleOwner = viewLifecycleOwner
binding.setVariable(BR.viewModel, mViewModel)
binding.executePendingBindings()
init()
}
ChidlFragment
class ChildFragment: ParentFragment<FragmentChildBinding, ChildViewModel>() {
@LayoutRes
override fun getLayoutResId() = R.layout.fragment_child
override val mViewModel: ChildViewModel by viewModel()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
override fun init() {
mViewModel.a()
}
a()
方法不执行任何操作,只是将变量值更改为一些随机文本.此变量在ChildFragment
中绑定到EditText
.这些是基本的数据绑定内容.问题的末尾提供了此方法的实现.
a()
method does nothing except changing the variable value to some random text. This varibale is bound to EditText
in ChildFragment
. These are basic data binding stuff. Implementation of this method is provided at the end of the question.
工作和a()
方法上方的代码已正确调用,但 EditText
我的ChildFragment中的值未更改.
The code above working and a()
method is being correctly called but EditText
value in my ChildFragment is not changing.
工作情况::如果我将代码更改为此,则一切正常.
WORKING CASE: If I change my code to this, everything is working fine.
ParentFragment
abstract class ParentFragment<T: ViewDataBinding>: Fragment() {
@LayoutRes
abstract fun getLayoutResId(): Int
protected lateinit var binding: T
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
return DataBindingUtil.inflate<T>(inflater, getLayoutResId(), container, false).apply { binding = this }.root
}
ChildFragment
class ChildFragment: ParentFragment<FragmentChildBinding>() {
@LayoutRes
override fun getLayoutResId() = R.layout.fragment_child
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val viewModel: ChildViewModel = getViewModel()
binding.viewModel = viewModel
binding.lifecycleOwner = this
binding.viewModel.a()
}
我的ChildViewModel
课.请注意,在两种情况下此类都是相同的:
My ChildViewModel
class. Note this class is the same in both of the cases:
class ChildViewModel(): ParentViewModel() {
var password: String = ""
//This function is being called in both cases. BUT ONLY IN THE SECOND CASE, setting value
//to "password" is being shown in the "EditText".
fun a () {
Log.d("-------", "ViewModel method called")
password = "asdasijdj1n2"
}
}
这可能是什么问题?
之所以这样做,是因为我想尽可能优化我的ParentFragment
,以避免子片段中的样板代码.
The reason why I doing this is that I would like to optmize my ParentFragment
as much as possible in order to avoid boilerplate code in the children fragments.
这里有两个问题,但只有一个是根本原因.
There are two issues here, but only one is the root cause.
之所以可以使用这种情况,是因为在数据绑定实际将其绑定到视图之前,已在视图模型中设置了password
属性的值.在不工作的情况下不会发生这种情况的原因与片段的结构无关-只是在视图模型中设置password
的值之前调用binding.executePendingBindings()
.这会强制数据绑定将password
的值绑定到视图,但由于当时是null
,因此您什么都看不到.
The reason the working case, well, works, is because the value of the password
property in the view model is set before databinding actually binds it to the view. The reason this doesn't happen in the not working case is nothing to do with the structure of your fragments - it's simply that binding.executePendingBindings()
is called before the value of password
is set in the view model. This forces databinding to bind the value of password
to view, but as it's null
at the time, you don't see anything.
这将我们带入问题的根本原因,即您的视图模型中有一个属性,该属性正在由数据绑定使用,并且该属性的值会更改,但是该属性是不可观察的.数据绑定需要知道何时更改其使用的属性的值,以便它可以更新使用这些属性的视图.无法正常工作的情况不起作用的原因是,调用binding.executePendingBindings()
时,数据绑定被强制将password
的null
值绑定到视图,并且无法得知后来更改了password
,因此它无法更新视图.
This brings us to the root cause of the issue, which is that you have a property in your view model which is being used by databinding and for which the value changes, but that property is not observable. Databinding needs to know when the value of properties it's using change, so that it can update the views that use those properties. The reason the not working case doesn't work is that databinding was forced to bind the null
value for password
to the view when binding.executePendingBindings()
was called, and had no way of knowing that password
was changed later, so it couldn't update the view.
通过数据绑定使属性可观察的两种方法是将它们声明为LiveData<T>
或ObservableField<T>
而不是T
(其中T
是数据类型).如果password
被声明为MutableLiveData<String>
或ObservableField<String>
,则您会在问题的两种情况下都看到该值出现在视图中.这是因为数据绑定会知道何时更改了值.
Two ways to make properties observable by databinding are to declare them as LiveData<T>
or ObservableField<T>
instead of T
(where T
is the type of data). If password
had been declared as a MutableLiveData<String>
or an ObservableField<String>
, you would have seen the value appear in the view in both cases in your question. This is because databinding would know when the value had changed.
因此,总而言之,对于在数据绑定中使用的视图模型中声明的属性(对于其值可以更改),使用LiveData
或ObservableField
是一个好习惯.这样,就不会出现诸如数据绑定将值绑定到视图时之类的时序问题.
So, in summary, it's good practice to use LiveData
or ObservableField
for properties declared in view models that are used in databinding and for which the value can change. That way, there's no potential for timing issues with things like when databinding binds values to views.