How to use android data binding to bind an interface?

Viewed 5498

i have the situation that i have a fragment, whose textfields will be filled by either a X1Object or a X2Object, which both are implementing the interface IXObject and extending the BaseObservable class provided by the Android DataBinding library, but contain additional different fields and behaviour. The IXObject contains the methods for getters and setters.

public interface IXObject {
    void setName(String name);
    String getName();
}

public class X1Object extends BaseObservable implements IXObject {
    private String name;

    @Override
    @Bindable
    public String getName() {
        return name;
    }

    @Override
    public void setName(String name) {
        this.name = name;
        notifyPropertyChanged(BR.name);
    }
}

public class X2Object extends BaseObservable implements IXObject {...}

Then i am trying to use a single layout file for the fragment, using Android DataBinding. The layout looks like this:

<?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"
xmlns:tools="http://schemas.android.com/tools">

<data>
    <variable name="xObject" type="com.test.x.model.IXObject"/>
</data>

<LinearLayout
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@{xObject.name}"/>

</LinearLayout>
</layout>

In the fragment class, i am applying the binding:

public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    MyFragmentBinding binding = DataBindingUtil.inflate(inflater, R.layout.my_fragment, container, false);

    View view = binding.getRoot();

    IXObject xobj = new X1Object();
    binding.setXObject(xobject);

    return view;
}

Unfortunately, using the IXObject as data binding reference, the method "addOnPropertyChangedCallback" in the BaseObservable class is never called.

Using X1Object directly for binding in the layout file and the binding class, everything works perfectly.

Can you help me how to achieve my goal to use an interface for binding?

Thank you.

5 Answers

When you declare a variable in your layout file you must take it literally. interfaces are not objects. I know Java supports generic inline objects where you can declare an interface as if it were an object, but in general they are not objects.

So when you say to the layout file to use a variable of the interface type, it really can't do much as it is not an object.

One thing you will notice and hate about android data binding is that it cannot infer types. So if you are trying to set a variable using an implementing class, but hoped to do it with an interface you will not have any luck.

You need to specify the class that will be used, not the interface that those classes inherit from. As the other answer pointed out, you can utilize a base class if your base class has the necessary methods.

Also quick note you can simply put the binding on the private member variable and it will still go through the getter and setters automatically.

The only other thing you could do is create an adapter that takes the interface or your interface or even Any with a cast, and sets the name for you. Which requires one more adapter in your already growing adapters class, but at least they are straight forward to do.

You can use your custom binding adapter. For example I want to handle click on ImageView to execute some action and if there is bound handler I want to image be visible (by default this btn is hidden)

header layout:

<layout ...>
    <data>
        <variable name="handler" type="Handler" />
    </data>
    ...
        <ImageButton
            android:visibility="invisible"
            ...
            app:setHandler="@{handler}"/>
    ...

interface plus custom binding adapter

internal fun interface Handler {
    fun invoke()
}

@BindingAdapter("setHandler")
internal fun setHandler(btn: ImageButton, handler: Handler?) {
    handler?.let { h ->
        btn.setOnClickListener { h.invoke() }
        btn.visibility = View.VISIBLE
    }
}

and then in some component where I included this layout with id header you want to execute your handling you can do

binding.header.handler = Handler {
    vm.dismiss()
}
Related