ymindstorm
ymindstorm

Reputation: 812

BindingAdapter not recognized as applicable to property with generics

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:

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

Answers (2)

Agent_L
Agent_L

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

Mohamed Kamel
Mohamed Kamel

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

Related Questions