数据绑定和MutableLiveData问题

时间:2019-07-16 06:54:27

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

我正在通过公开公开的函数setRefreshing(app:XML中的刷新)将MutableLiveData绑定到SwipeRefreshLayout上,到那时一切正常……但是让我们介绍一下我的应用程序体系结构。

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

然后我有两个继承自此摘要的ViewModel(我将它们命名为FirstViewModel和SecondViewModel),将其命名为BaseRefreshViewModel。首先,我有两个几乎相同的XML文件,仅在“ 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,它也可以开始编译(我当然不能像这样保留它,因为我有我绑定到适配器的ViewModels中的对象的其他列表)。

这是我的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的生命周期优势。我想这只是说明Databinding编译器现在是如何被窃听的。

摘要:

工作(两个不同的xml,几乎相同)

  • BaseRefreshViewModel(isRefreshing:MutableLiveData)
    • FirstViewModel
      • first_list_layout.xml(导入FirstViewModel)
    • SecondViewModel
      • second_list_layout.xml(导入SecondViewModel)

工作中(一个xml文件,但没有LiveData)

  • BaseRefreshViewModel(isRefreshing:ObservableBoolean)
    • FirstViewModel
      • list_layout.xml(导入BaseRefreshViewModel)
    • SecondViewModel
      • list_layout.xml(导入BaseRefreshViewModel)

不起作用(一个带有LiveData的xml文件)

  • BaseRefreshViewModel(isRefreshing:MutableLiveData)
    • FirstViewModel
      • list_layout.xml(导入BaseRefreshViewModel)
    • SecondViewModel
      • list_layout.xml(导入BaseRefreshViewModel)

5 个答案:

答案 0 :(得分:1)

今天也遇到了此错误。 一种可能但很麻烦的解决方法是使BindAdapter接受Object,然后将其强制转换为所需的对象。

更改 @BindingAdapter("refresh") fun setRefreshing(view: SwipeRefreshLayout, refreshing: Boolean)

@BindingAdapter("refresh") fun setRefreshing(view: SwipeRefreshLayout, refreshing: Object)

,然后将其refreshing投射到Boolean

答案 1 :(得分:1)

另一种选择是使用数据绑定3.4.1,它可以按预期工作。

    dataBinding {
        enabled = true
        version = "3.4.1"
    }

答案 2 :(得分:1)

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

apply plugin: 'kotlin-kapt'

此后,将LiveData<T>绑定到字段时将其解包到T中。

为了记录,我还包括了这些库

    // 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'

答案 3 :(得分:0)

嗨,希望你做得好。

问题出在app:refreshing="@{viewModel.isRefreshing}"

属性 app:refershing 仅接受布尔值值。而您正尝试给它一个 LiveData 值。导致

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

所以您可以做的是:

  1. Data 标签

    中创建 Boolean 类型的变量
    <variable
        name="refreshing"
        type="Boolean" />
    
  2. 观察您的MutableLiveData

  3. 在观察器中将 Boolean 变量设置为 DataBinding 变量。 yourBinding.setRefreshing(yourObserverBooleanVariable);

注意:我是根据Java语法编写的

答案 4 :(得分:0)

在您的XML中:

 <android.support.v4.widget.SwipeRefreshLayout
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 app:refreshing="@{viewModel.isLoading}"
 app:onRefreshListener="@{() -> viewModel.onRefresh()}">

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

    </android.support.v4.widget.SwipeRefreshLayout>

在您的ViewModel中:

    public MutableLiveData <Boolean >isLoading = new MutableLiveData ();

/* Needs to be public for Databinding */
public void onRefresh() {
    isLoading.setValue(true);
    // your logic
}

public void onError(Exception oops){
    isLoading.setValue(false);
    Log.e("Stack", oops);
}