Android 如果使用setVariable()方法设置ViewModel,则数据绑定不起作用

Android 如果使用setVariable()方法设置ViewModel,则数据绑定不起作用,android,android-databinding,koin,Android,Android Databinding,Koin,我有ParentFragment和ChildFragment。我用Koin来表示DI 在一种情况下,数据绑定不起作用,而在另一种情况下,数据绑定起作用 不起作用的情况:ParentFragment abstract class ParentFragment<T: ViewDataBinding, V: ParentViewModel>: Fragment() { @LayoutRes abstract fun getLayoutResId(): Int a

我有
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()
    }

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()
    }

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
    }

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()

    }
a()。此变量绑定到
ChildFragment
中的
EditText
。这些是基本的数据绑定功能。问题末尾提供了此方法的实现

正在正确调用上面的代码和
a()
方法,但我的ChildFragment中的
值未更改

工作案例:如果我将代码更改为此,则一切正常

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()
    }

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()
    }

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
    }

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()

    }
My
ChildViewModel
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
属性的值是在数据绑定实际将其绑定到视图之前设置的。在不工作的情况下不会发生这种情况的原因与片段的结构无关-只是在视图模型中设置
password
的值之前调用了
binding.executePendingBindings()
。这迫使数据绑定将
password
的值绑定到视图中,但由于它当时是
null
,所以您什么也看不到

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

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


因此,总的来说,对于在数据绑定中使用的视图模型中声明的属性以及值可以更改的属性,最好使用
LiveData
observefield
。这样,数据绑定将值绑定到视图时就不会出现计时问题。

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

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()
    }
抽象类ParentFragment:Fragment(){
抽象乐趣提供设备模型():V
受保护的lateinit变量绑定:T
受保护的抽象值mViewModel:V
覆盖创建视图(充气机:布局充气机,容器:ViewGroup?,savedInstanceState:Bundle?):视图{
super.onCreateView(充气机、容器、savedInstanceState)
返回DataBindingUtil.inflate(inflater,getLayoutResId(),container,false)
}
覆盖已创建的视图(视图:视图,保存状态:捆绑?){
super.onViewCreated(视图,savedInstanceState)
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

...
class-ChildFragment:ParentFragment(){
...
通过viewModel()覆盖val mViewModel:ChildViewModel
覆盖已创建的视图(视图:视图,保存状态:捆绑?){
super.onViewCreated(视图,savedInstanceState)
}
覆盖有趣的ProviderViewModel()=mViewModel
...

}

实际问题是什么?
binding.viewModel=viewModel
这会将视图模型绑定到布局,在它不起作用的情况下,我看不到布局。这就是为什么据我所知它不起作用。换句话说,在选项1中,您在哪里分配
受保护的lateinit var binding:t
这就是案例1不起作用的原因。请仔细查看代码。
protected lateinit var binding:T
是在
ParentFragment
onCreateView
中分配的。我猜方法是
setVariable()
在案例1中出现了一些问题。@choorba0t也使用了
LiveData
进行了检查,并且它正在工作!如果您在数据绑定使用的视图模型中有一个属性,并且该属性的值可以更改,那么在绝大多数情况下,我认为您应该使用
LiveData
observefield
。此外,我认为您看到的问题正是这个,而不是您是否正在调用
executePendingBindings
。很好的解释!现在更清楚为什么会发生这个wierd问题我应该将其定义为var吗?@issamuxmaybe我错了,但是通过viewModel()不能与var一起使用,而且它甚至不能在Android studio中编译!!这对你有用吗?我使用的是koin androidx 2.1.5,你必须将它定义为val,因为v的延迟初始化