Reputation: 325
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)
WORKING (one xml file, but not LiveData)
NOT WORKING (one xml file with LiveData)
Upvotes: 2
Views: 10282
Reputation: 4116
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'
Upvotes: 3
Reputation: 25
Another option is to use databinding 3.4.1, which works as expected.
dataBinding {
enabled = true
version = "3.4.1"
}
Upvotes: 1
Reputation: 6141
Encountered this error today too. One possible, but a little ugly workaround is to make your BindAdapter accept Object and then cast it to whatever you need.
Change
@BindingAdapter("refresh")
fun setRefreshing(view: SwipeRefreshLayout, refreshing: Boolean)
to @BindingAdapter("refresh")
fun setRefreshing(view: SwipeRefreshLayout, refreshing: Object)
and then cast refreshing
it to Boolean
Upvotes: 1
Reputation: 525
In your 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>
In your 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);
}
Upvotes: 0
Reputation: 1710
Hi Hope you doing good.
The problem is in the app:refreshing="@{viewModel.isRefreshing}"
.
The attribute app:refershing accept only Boolean value. whereas you are trying to give it a LiveData value. which results in
Caused by: java.lang.ClassCastException: java.lang.Boolean cannot be cast to androidx.lifecycle.MutableLiveData
so what you can do is:
Create a variable of Boolean type in Data tag
<variable
name="refreshing"
type="Boolean" />
Observe your MutableLiveData
yourBinding.setRefreshing(yourObserverBooleanVariable);
Note: I wrote it according to java syntax
Upvotes: 0