如果使用 setVariable() 方法设置 ViewModel,则 DataBinding 不起作用
DataBinding is not working if setVariable() method is used to set ViewModel
我有 ParentFragment
和 ChildFragment
。我正在使用 Koin 进行 DI。
在一种情况下,数据绑定不起作用,而在另一种情况下,它起作用了。
不工作案例: 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()
方法被正确调用 但是 EditText
我的 ChildFragment 中的值没有改变。
工作案例: 如果我将代码更改为此,一切正常。
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
class。请注意,这 class 在两种情况下都是相同的:
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
以避免子片段中的样板代码。
这里有两个问题,但只有一个是根本原因。
工作案例之所以有效,是因为视图模型中 password
属性 的值是在数据绑定实际将其绑定到视图之前设置的。在不工作的情况下不会发生这种情况的原因与片段的结构无关 - 只是 binding.executePendingBindings()
在视图模型中设置 password
的值之前被调用。这会强制数据绑定将 password
的值绑定到视图,但因为它当时是 null
,所以您什么也看不到。
这让我们找到了问题的根本原因,即您的视图模型中有一个 属性 正被数据绑定使用并且其值发生变化,但是 属性 是不可观察的。数据绑定需要知道它使用的属性值何时发生变化,以便它可以更新使用这些属性的视图。不工作的情况不起作用的原因是当 binding.executePendingBindings()
被调用时,数据绑定被迫将 password
的 null
值绑定到视图,并且无法知道 password
后来改了,所以没法更新视图。
通过数据绑定使属性可观察的两种方法是将它们声明为 LiveData<T>
或 ObservableField<T>
而不是 T
(其中 T
是数据类型)。如果 password
被声明为 MutableLiveData<String>
或 ObservableField<String>
,您会在问题的两种情况下看到该值出现在视图中。这是因为数据绑定会知道值何时更改。
因此,总而言之,对视图模型中声明的属性使用 LiveData
或 ObservableField
是一种很好的做法,这些属性在数据绑定中使用并且其值可以更改。这样,当数据绑定将值绑定到视图时,就不会出现时间问题。
您可以通过为每个子片段提供视图模型来解决您的问题。您可以更改
abstract class ParentFragment<T: ViewDataBinding, V: ParentViewModel>:Fragment() {
abstract fun provideViewModel(): V
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, provideViewModel().apply { mViewModel = this})
binding.executePendingBindings()
init()
}
在子片段中;
class ChildFragment: ParentFragment<FragmentChildBinding, ChildViewModel>() {
...
override val mViewModel: ChildViewModel by viewModel()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
override fun provideViewModel() = mViewModel
...
}
我有 ParentFragment
和 ChildFragment
。我正在使用 Koin 进行 DI。
在一种情况下,数据绑定不起作用,而在另一种情况下,它起作用了。
不工作案例: 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()
方法被正确调用 但是 EditText
我的 ChildFragment 中的值没有改变。
工作案例: 如果我将代码更改为此,一切正常。
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
class。请注意,这 class 在两种情况下都是相同的:
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
以避免子片段中的样板代码。
这里有两个问题,但只有一个是根本原因。
工作案例之所以有效,是因为视图模型中 password
属性 的值是在数据绑定实际将其绑定到视图之前设置的。在不工作的情况下不会发生这种情况的原因与片段的结构无关 - 只是 binding.executePendingBindings()
在视图模型中设置 password
的值之前被调用。这会强制数据绑定将 password
的值绑定到视图,但因为它当时是 null
,所以您什么也看不到。
这让我们找到了问题的根本原因,即您的视图模型中有一个 属性 正被数据绑定使用并且其值发生变化,但是 属性 是不可观察的。数据绑定需要知道它使用的属性值何时发生变化,以便它可以更新使用这些属性的视图。不工作的情况不起作用的原因是当 binding.executePendingBindings()
被调用时,数据绑定被迫将 password
的 null
值绑定到视图,并且无法知道 password
后来改了,所以没法更新视图。
通过数据绑定使属性可观察的两种方法是将它们声明为 LiveData<T>
或 ObservableField<T>
而不是 T
(其中 T
是数据类型)。如果 password
被声明为 MutableLiveData<String>
或 ObservableField<String>
,您会在问题的两种情况下看到该值出现在视图中。这是因为数据绑定会知道值何时更改。
因此,总而言之,对视图模型中声明的属性使用 LiveData
或 ObservableField
是一种很好的做法,这些属性在数据绑定中使用并且其值可以更改。这样,当数据绑定将值绑定到视图时,就不会出现时间问题。
您可以通过为每个子片段提供视图模型来解决您的问题。您可以更改
abstract class ParentFragment<T: ViewDataBinding, V: ParentViewModel>:Fragment() {
abstract fun provideViewModel(): V
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, provideViewModel().apply { mViewModel = this})
binding.executePendingBindings()
init()
}
在子片段中;
class ChildFragment: ParentFragment<FragmentChildBinding, ChildViewModel>() {
...
override val mViewModel: ChildViewModel by viewModel()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
override fun provideViewModel() = mViewModel
...
}