Reputation: 812
Matching ObservableList<Subclass>
to @BindingAdapter
declaration with ObservableList<Baseclass>
behaves in a strange way.
I am trying out the android databinding library in context of MVVM and stumbled upon a behavior which I do not understand and have not found any resources for so far.
The setup:
Min SDK: 19
Target SDK: 26
Build Tools Version: 27.0.1
I want to bind an android.databinding.ObservableList<Model>
to a RecyclerView
by using a custom binding adapter which takes the list, wraps it into a generic RecyclerView.Adapter
and assigns it to the RecyclerView
.
My binding adapter is set up like this:
@BindingAdapter({"items"})
public static void bindAdapterWithDefaultBinder(@NonNull RecyclerView recyclerView,
@Nullable ObservableList<Model> items){
... implementation ...
}
My viewmodel has the following property:
public final ObservableList<Repo> repos = new ObservableArrayList<>();
Repo
in this case implements the interface Model
.
And the layout file contains
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:items="@{vm.repos}"
/>
When compiling I receive the following error:
Error:Gradle: Execution failed for task ':app:compileDebugJavaWithJavac'.
> java.lang.RuntimeException: Found data binding errors.
****/ data binding error ****msg:Cannot find the setter for attribute 'app:items' with parameter type android.databinding.ObservableList<_my-package-name_.Repo> on android.support.v7.widget.RecyclerView.
So I thought this has something to do with the type Repo
not being recognized as a valid substitute for Model
in the binding converter declaration. Strange, but sure enough - if I change the declaration to use ObservableList<Repo> items
the code compiles fine and works. But this is not a viable solution for me since this would force me to write a binding adapter for every single model or view model type I intend to use. So after some experimenting I found a different solution:
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:items="@{(Object)vm.repos}"
/>
Now this solution compiles with the ObservableList<Model> items
adapter declaration and propagates all change events correctly at runtime. So basically I cast the ObservableList<Repo>
to Object
and it is interpreted as matching the adapter definition. What?????
Keep in mind all of this happens at compile time where the exact types are known to the code generator so no type erasure is happening.
So in order my questions are:
Object
resolve the problem?P.S.:
For more context on the issue I am basically working my way through this example code which I found to be a great source of inspiration: https://github.com/manas-chaudhari/android-mvvm/blob/master/Documentation/GettingStarted.md
But notice how the author cheekily "casts" all his model objects to the base class type to avoid this exact problem here.
Upvotes: 2
Views: 948
Reputation: 5411
That's because List
is not covariant in Java. That means that List<Subclass>
is not List<Baseclass>
so there is no reason for binding engine to consider your adapter. You need:
@BindingAdapter({"items"})
public static <T extends Model> void bindAdapterWithDefaultBinder(@NonNull RecyclerView recyclerView,
@Nullable ObservableList<T> items){
and generics all the way down.
Upvotes: 1
Reputation: 937
I think you should remove braces to be like this:
@BindingAdapter("items")
public static void bindAdapterWithDefaultBinder(@NonNull RecyclerView recyclerView, @Nullable ObservableList<Model> items) {
... implementation ...
}
Upvotes: 0