Problem with Databinding and MutableLiveData

miecio picture miecio · Jul 16, 2019 · Viewed 8.1k times · Source

NOVEMBER 2019 UPDATE - it is working as intended now on the latest version.

ORIGINAL POST:

I'm binding MutableLiveData to my SwipeRefreshLayout via publicly exposed function setRefreshing (app:refreshing in XML) and everything works fine by the time... But let in introduce my app architecture.

I have abstract ViewModel with MutableLiveData when I change its value according to refresh the status.

Then I have two ViewModels (let me name them FirstViewModel and SecondViewModel) inherited from this abstract, name it BaseRefreshViewModel. First I had two practically identical XML files, differing only with "data" node when in first XML I import FirstViewModel and in second - corresponding SecondViewModel.

I was horrible, so I merged this into one XML and import this 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>

And then compiler start going crazy - it says:

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.

Ok, so I wrote my own BindingAdapter (changing of course to app:refresh in SwipeRefreshLayout):

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

Still the same issue, then I changed BindingAdapter to:

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

And it starts compiling, but after run my app crash with error:

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

No shit Sherlock... What is funny that when I change import in my XML file from BaseRefreshViewModel to FirstViewModel/SecondViewModel it starts compiling just fine even without my BindingAdapter (I can't leave it like this of course because I have a different list of object in ViewModels which I'm binding to my adapter).

Here is my ViewModel initialization in fragment:

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
    }

And ViewModel itself:

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

}

How can I overpass this without going back to creating two XML files with different ViewModel imported?

I'm using Android Studio 3.5 Beta 5 just to take advantage with improved error messages with DataBinding.

UPDATE:

When I change MutableLiveData to ObservableBoolean() it compile and run fine... But I don't wanna stick with this, I want to use LiveData with its Lifecycle advantages. It's just shows how Databinding compiler is bugged right now I think.

SUMMARY:

WORKING (two different xml, practically the same)

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

WORKING (one xml file, but not LiveData)

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

NOT WORKING (one xml file with LiveData)

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

Answer

Fernando Gallego picture Fernando Gallego · Oct 29, 2019

Worked for me after applying the plugin kotlin-kapt on a kotlin project

apply plugin: 'kotlin-kapt'

after that, the LiveData<T> is unwrapped into T when bound to fields.

For the record, I also include these libraries

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