android - 数据绑定(bind)和 MutableLiveData 的问题

标签 android android-databinding android-livedata mutablelivedata android-binding-adapter

2019 年 11 月更新 - 它现在在最新版本上按预期运行。

原帖:

我通过公开公开的函数 setRefreshing(XML 中的 app:refreshing)将 MutableLiveData 绑定(bind)到我的 SwipeRefreshLayout,到时候一切都正常......但是让我们介绍一下我的应用程序架构。

当我根据刷新状态更改其值时,我有带有 MutableLiveData 的抽象 ViewModel。

然后我有两个从这个抽象继承的 ViewModel(让我将它们命名为 FirstViewModel 和 SecondViewModel),将其命名为 BaseRefreshViewModel。首先,我有两个几乎相同的 XML 文件,仅当在第一个 XML 中导入 FirstViewModel 和在第二个 XML 中导入对应的 SecondViewModel 时,“数据”节点不同。

我太糟糕了,所以我将其合并到一个 XML 中并导入此 BaseRefreshViewModel (list_layout.xml):

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable name="viewModel"
                type="my.package.BaseRefreshViewModel" />
    </data>

    <androidx.coordinatorlayout.widget.CoordinatorLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/coordinator_layout">

        <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:refreshing="@{viewModel.isRefreshing}"
                android:id="@+id/swipe_layout">

            <androidx.recyclerview.widget.RecyclerView
                    android:id="@+id/station_list"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    app:adapter="@{viewModel.stations}"/>

        </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
    </androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

然后编译器开始变得疯狂 - 它说:

Cannot find a setter for <androidx.swiperefreshlayout.widget.SwipeRefreshLayout app:refreshing> that accepts parameter type 'androidx.lifecycle.MutableLiveData'

If a binding adapter provides the setter, check that the adapter is annotated correctly and that the parameter type matches.

好吧,所以我编写了自己的 BindingAdapter(当然更改为 SwipeRefreshLayout 中的 app:refresh):

@BindingAdapter("refresh")
fun setRefreshing(view: SwipeRefreshLayout, refreshing: Boolean) {
    view.isRefreshing = refreshing
}

还是同样的问题,然后我将 BindingAdapter 更改为:

@BindingAdapter("refresh")
fun setRefreshing(view: SwipeRefreshLayout, refreshing: MutableLiveData<Boolean>) {
    refreshing.value?.let { view.isRefreshing }
}

它开始编译,但运行后我的应用程序崩溃并出现错误:

Caused by: java.lang.ClassCastException: java.lang.Boolean cannot be cast to androidx.lifecycle.MutableLiveData

没有狗屎Sherlock...有趣的是,当我将XML文件中的导入从BaseRefreshViewModel更改为FirstViewModel/SecondViewModel时,即使没有我的BindingAdapter,它也开始编译得很好(我当然不能像这样离开它,因为我有ViewModel 中的不同对象列表,我将其绑定(bind)到我的适配器)。

这是我在 fragment 中的 ViewModel 初始化:

lateinit var stationViewModel: FirstViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        stationViewModel = ViewModelProviders.of(requireActivity()).get(FirstViewModel::class.java)
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        binding = DataBindingUtil.inflate(inflater, R.layout.list_layout, container, false)
        binding.viewModel = stationViewModel
        binding.lifecycleOwner = this
        return binding.root
    }

还有 ViewModel 本身:

abstract class BaseRefreshViewModel(application: Application) : AndroidViewModel(application) {

    val isRefreshing = MutableLiveData<Boolean>().apply { value = false }


    val receiver = object : StatusReceiver.Receiver {
        override fun onReceiveResult(resultCode: Int, resultData: Bundle) {
            when (resultCode) {
                StatusReceiver.STATUS_RUNNING -> isRefreshing.value = true
                StatusReceiver.STATUS_IDLE -> isRefreshing.value = false
                StatusReceiver.STATUS_NO_CONNECTION -> isRefreshing.value = false
                StatusReceiver.STATUS_ERROR -> isRefreshing.value = false
            }
        }
    }

    abstract fun refresh()

}

如何绕过这个问题,而不需要返回创建两个导入了不同 ViewModel 的 XML 文件?

我使用 Android Studio 3.5 Beta 5 只是为了利用 DataBinding 改进的错误消息。

更新:

当我将 MutableLiveData 更改为 ObservableBoolean() 时,它编译并运行良好...但我不想坚持这样做,我想使用 LiveData 及其生命周期优势。我认为它只是显示了数据绑定(bind)编译器现在是如何被窃听的。

摘要:

正在工作(两个不同的 xml,实际上相同)

  • BaseRefreshViewModel(正在刷新:MutableLiveData)
    • 第一个 View 模型
      • first_list_layout.xml(导入 FirstViewModel)
    • 第二个 View 模型
      • second_list_layout.xml(导入 SecondViewModel)

正在运行(一个 xml 文件,但不是 LiveData)

  • BaseRefreshViewModel(isRefreshing:ObservableBoolean)
    • 第一个 View 模型
      • list_layout.xml(导入 BaseRefreshViewModel)
    • 第二个 View 模型
      • list_layout.xml(导入 BaseRefreshViewModel)

不工作(一个包含 LiveData 的 xml 文件)

  • BaseRefreshViewModel(正在刷新:MutableLiveData)
    • 第一个 View 模型
      • list_layout.xml(导入 BaseRefreshViewModel)
    • 第二个 View 模型
      • list_layout.xml(导入 BaseRefreshViewModel)

最佳答案

应用插件后为我工作 kotlin-kapt在 kotlin 项目上

apply plugin: 'kotlin-kapt'

之后,LiveData<T>被展开为T当绑定(bind)到字段时。

根据记录,我还包含这些库

    // Lifecycle
    implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0-rc01'
    implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-rc01'
    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-rc01'

关于android - 数据绑定(bind)和 MutableLiveData 的问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57051586/

相关文章:

android - cordova-plugin-admob-free 在构建过程中产生错误?

android - 以编程方式将标准小部件添加到主屏幕

android - 使用 WindowManager.updateViewLayout() 获取 View 以动画化到其新位置

android - 是否可以强制 LiveData 值的不可空性?

android - 如何使用 mvvm 简化介绍屏幕逻辑

android - 释放 key 以签署多个 Android 应用程序

如果值为真,Android 数据绑定(bind)设置填充

android - float 标签提示 (TextInputLayout) 不适用于 Android 数据绑定(bind)

android - 在android上覆盖布局和数据绑定(bind)生成的类

android - 在Spinner中使用LiveData