如果使用setVariable()方法设置ViewModel,则DataBinding无法正常工作

时间:2019-12-11 10:43:34

标签: android android-databinding koin

我有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()方法除了将变量值更改为一些随机文本外,什么也不做。此变量在EditText中绑定到ChildFragment。这些是基本的数据绑定内容。问题的末尾提供了此方法的实现。

正常工作和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以避免在子片段中使用样板代码。

2 个答案:

答案 0 :(得分:1)

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

工作用例正常工作的原因是,在数据绑定实际将其绑定到视图之前,已经在视图模型中设置了password属性的值。在不工作的情况下不会发生这种情况的原因与片段的结构无关-只是在视图模型中设置binding.executePendingBindings()的值之前调用password。这会强制数据绑定绑定要查看的password的值,但是由于当时为null,因此您什么都看不到。

这将我们带入问题的根本原因,即您的视图模型中有一个属性,该属性正在由数据绑定使用,并且该属性的值会更改,但该属性不可观察。数据绑定需要知道何时使用的属性值发生更改,以便可以更新使用这些属性的视图。无法正常工作的情况不起作用的原因是,数据绑定被迫在调用null时将password的{​​{1}}值绑定到视图,并且无法知道{ {1}}后来进行了更改,因此无法更新视图。

通过数据绑定使属性可观察的两种方法是将它们声明为binding.executePendingBindings()password而不是LiveData<T>(其中ObservableField<T>是数据类型)。如果将T声明为Tpassword,则您会在问题的两种情况下都看到该值出现在视图中。这是因为数据绑定会知道何时更改了值。

因此,总而言之,对于在数据绑定中使用的视图模型中声明的属性可以使用MutableLiveData<String>ObservableField<String>,这些属性的值可以更改。这样,就不会出现诸如数据绑定将值绑定到视图时之类的时序问题。

答案 1 :(得分:0)

您可以通过为每个子片段提供视图模型来解决您的问题。您可以更改

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

...

}