首页 > 解决方案 > 如果使用 setVariable() 方法设置 ViewModel,则 DataBinding 不起作用

问题描述

我有ParentFragmentChildFragment。我正在使用 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()方法除了将变量值更改为一些随机文本之外什么都不做。这个变量是必然EditTextChildFragment。这些是基本的数据绑定东西。问题末尾提供了此方法的实现。

上面的代码工作和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 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尽可能地优化我的代码,以避免子片段中的样板代码。

标签: androidandroid-databindingkoin

解决方案


这里有两个问题,但只有一个是根本原因。

工作案例之所以有效,是因为password视图模型中的属性值是在数据绑定实际将其绑定到视图之前设置的。在不工作的情况下不会发生这种情况的原因与片段的结构无关 - 它只是在视图模型中设置binding.executePendingBindings()的值之前调用。password这会强制数据绑定将 的值绑定password到视图,但null在当时,您什么也看不到。

这给我们带来了问题的根本原因,即您的视图模型中有一个属性正在被数据绑定使用并且值发生变化,但该属性是不可观察的。数据绑定需要知道它使用的属性值何时发生变化,以便它可以更新使用这些属性的视图。不工作的情况不起作用的原因是数据绑定在被调用时被迫将null值绑定password到视图binding.executePendingBindings(),并且无法知道password后来发生了更改,因此它无法更新视图。

通过数据绑定使属性可观察的两种方法是将它们声明为LiveData<T>ObservableField<T>代替T(数据类型在哪里T)。如果password已被声明为 aMutableLiveData<String>或 an ObservableField<String>,您会在问题的两种情况下看到该值出现在视图中。这是因为数据绑定会知道值何时更改。

因此,总而言之,使用LiveDataObservableField用于在视图模型中声明的属性是一种很好的做法,这些属性用于数据绑定并且值可以更改。这样,就不会出现诸如数据绑定将值绑定到视图之类的时间问题。


推荐阅读