Diving into Android Data Binding & Inverse Data Binding

Why read this?

Heng Lim
Heng Lim
Jul 30, 2017 · 9 min read

Android data binding library, or more recently, inverse data binding, is a relatively new feature and not a lot of information is floating around on the internet. While the information provided by Android developers are succinct and to the point, it is still difficult for a beginner to decipher that information without covering some ground work. If like me, you have consumed the official documentation and want to dive in a little deeper, read this.

The basic of data binding

Data binding is a great way to cut down on your Android code base. It does this by generating binding methods for you behind the scene. When enabled, the data binding generator will generate code in the build/generated path of your project module. The basic for how to set up and use data binding can be found on deveoper.android.com:

  1. Data Binding Library
  2. InverseDataBindingAdapter

In the context of a Java Bean object, the data binder “gets” the data from the object, and “sets” the data to the View. Conversely, inverse data binder “gets” the data from the View and “sets” the data to the object. To put it simply:

  • One Way Data Binding:
    OBJECT.get → VIEW.set
  • Two Way Data Binding:
    OBJECT.get → VIEW.set → VIEW.get → OBJECT.set

The data binding library generates the code to get or set the object or view when you build your module. Any changes to your layout file, will not generate new changes on the fly, you will need to build your module for the data binding library to parse your layout and generate the binding code. So if certain things don’t link up, build your module or project, and the changes will be generated regardless of any errors in your code.

Data binding mechanics

From the Android documentation you would have known that to bind a variable you will need to add @{variable.field} or @={variable.field} to your attribute. However, what does this really mean? The @/= symbol and braces around your variable names are keywords that the binder use to determine the start and end of your variable and what code to generate.

In the case of @{variable.field} the binder will generate a piece of code to get the value by calling variable.getField(). Note that the binder implicitly prefix the “get” word if it is not provided. So @{variable.getField()} is an explicit valid option.

In the case of @={variable.field} the binder will create the same code as before, but with additional code for getting the value from the View and setting it back to your object. Again, the get word is prefixed.

When does the data get bound?

In the case of one way data binding, the binding occurs once during the creation of your view. This is why you need to manually set the content view or inflate the layout using the DataBindingUtil. The inflation process and later when you “execute the binding” causes the binder to get all the values from your object and set it to the view. You can later set up an Observeable object to notify the binder of any changes to the object’s value, which causes the binding to recur. More on this later.

When does the data get bound for Inverse Data Binding?

While one way data binding occurs once at the start, inverse binding requires that the data flow object → view, and view → object. When you tell the data binder to perform an inverse operation by assigning the @={} keyword, it needs to know “when” to do it. The “when” is an event provided by the view. For example, View.onClick is an event and is fired off when a listener is provided via the interface “View.OnClickListener”.

The data binding library has certain events already defined, which makes it possible for certain stock Android view and attribute combination to work with two way data binding. They are listed on this post.

So what if the event is not provided by the library? Simply put, the two way binding will not work. The binder will generate an error during compile time: “Could not find event fieldAttrChanged”.

You’ll need to define one yourself. This is when BindingAdapters and InverseBindingAdapters come into play.

BindingAdapter and BindingMethod

BindingAdapter annotation allows you to define your own custom method for any given attribute or attributes.

Normally, you will not have to define the BindingAdapter if the name of the attribute is the same as the Java Bean field name. For example, if the attribute name is “text” then the binder will look for “setText(FieldType)” in the view. The FieldType can be of any type, returned by the object’s get method. In the case of a text that deals with a String value this is the flow:

String getText() from OBJECT → void setText(String) to VIEW

So if your object returns a String, the binder looks for a method named setText with a String parameter. If the object returns an int, then it will look for a method with an int parameter and so on.

This is the default behaviour, but I prefer to explicitly define my own methods when creating a custom attribute, this way the code is more clear about its intent, instead of hiding behind the smoke and mirror of the library’s default behaviour.

One way to explicitly define a custom method for a given attribute is to use the BindingAdapter annotation above a method name. You can read more about it from the official documentation.

Any method will suffice, but it must be made accessible to the binder. As per the documentation, the method must be public static. Or if it’s an instance method then it will need the DataBindingComponent as the first parameter (which will not be discussed here). When defined this way, the method must include the view’s object as the first parameter, and the value or values thereafter. Each value corresponds to the value provided in the BindingAdapter annotation. As an example from the documentation:

@BindingAdapter({"android:onClick", "android:clickable"})public static void setOnClick(View view, View.OnClickListener clickListener, boolean clickable) {     view.setOnClickListener(clickListener);
view.setClickable(clickable);
}

Notice that the first parameter is a view, and the proceeding parameters correspond to the attributes provided by the BindingAdapter above the method.

If you’re extending a custom view, and need to define a custom attribute and method, use the BindingMethod annotation. BindingMethod allows you to map an attribute to a view’s method. This is necessary when the attribute’s name is different to the view’s method name. For example, if your attribute’s name is text_color but the view’s method is setTextColor(int), then the default behaviour will not kick in because it is expecting settext_color().

To map the above method, do this:

@BindingMethods({

@BindingMethod(
type = CustomView.class,
attribute = "text_color",
method = "setTextColor"
)
})
public class CustomView extends View { public void setTextColor(int color) {
//...
}
}

As I mentioned, I prefer to explicitly map all the methods either as part of the custom view’s instance or from a public static context. Some differences between the two:

BindingAdapter with public static method:

  • requires a view object to be passed as the first parameter
  • can have multiple attributes assigned to one method, allowing you to define the order of operation if need be

BindingMethod with a view’s instance method:

  • the view’s object is implicitly available to the method, view object is not required to be passed as a parameter
  • encapsulates the method in its view

InverseBindingAdapter and InverseBindingMethod

Here’s the fun part that can take quite a while of getting used to.

As mentioned, to inversely bind a data object to a view, the binder needs to know “when” to do this through an event. For every attribute assigned with @={}, the binder will generate an additional attribute suffixed with “attrChanged”. So if the attribute is named “text” then the generated attribute is called “textAttrChanged”. This generated attribute is responsible for firing off the inverse binding event, which causes the binder to set the value to the object. Let’s deep dive into what an inverse binding operation may look like:

OBJECT.get → VIEW.set → VIEW.onClick → attrChanged.onChange → VIEW.get → OBJECT.set

So the binder gets the value from the object, then sends it to the view during initialisation, but then somehow it needs to know when to send the value from the view back to the object. In the above example flow, the event is the onClick event. When the user clicks on the view, we want to notify the binder to send the value back to the object.

OK, that’s great, but how do you actually tell the binder to do this? By using the annotation InverseBindingAdapter or InverseBindingMethod. You can read more about it on the official documentation here.

Much like BindingAdapter, InverseBindingAdapter allows you to define an inverse method anywhere, provided that you pass the view to it. Like this:

@InverseBindingAdapter(attribute = "android:text_color", event = "text_colorAttrChanged)
public static int getTextColor(TextView view, int color) {
//...
}

And much like BindingMethod, InverseBindingMethod allows you to explicitly remap a view’s method to an attribute. Like this:

@InverseBindingMethods({

@InverseBindingMethod(
type = CustomView.class,
attribute = "text_color",
method = "getTextColor"
)
})
public class CustomView extends View {public int getTextColor() {
//...
}
}

OK, but that doesn’t explain the event. Where is it defined?

Well, you have to define it yourself. As I mentioned earlier, some events have already been pre-defined for some stock android Views, which makes it possible for two way data binding to work by default. To create your own, you will need to define the attrChanged method for each attribute that requires two-way binding. You can do this using the BindingAdapter or BindingMethod annotation. Like this:

@BindingAdapter({"text_colorAttrChanged"})public static void setTextColorChanged(View view, final InverseBindingListener listener) {    view.setOnClickListner(new OnClickListener() {        public void onClick() {
listener.onChange()
}
});
}

or this:

@BindingMethods({

@BindingMethod(
type = CustomView.class,
attribute = "text_colorAttrChanged",
method = "setColorChangeListener"
)
})
public class CustomView extends View {public void setColorChangeListener(InverseBindingListener listener){ mColorChangeListaner = listener; //...
}

The above method simply passes the InverseBindingListener for you to work with. It is called when “executeBinding()” is called after you inflate and set the binding. It is up to you on what happens to it. You can call it as part of another listener like in the first example, or assign it to an instance variable to do other neat things with it like the second example.

One neat thing about the second example is that you can have multiple things firing off the event. In the second example we have not yet notified the InverseBindingListener. We’ll need to notify it when something happens. So for an onCilck event, do this:

public class CustomView extends View implements View.OnClickListener {
public CustomView() {
setOnClickListener(this)
}
private InverseBindingListener mInverseBindingListener; public void setColorChangeListener(InverseBindingListener listener) {
mInverseBindingListener = listener;
}
public void onClick(View v) {
mInverseBindingListener.onChange() // Notify the binder
}
// ...
}

The above is a custom view that defines its own onClick listener. When the listener is notified of the onClick event, it will then call the InverseBindingListener provided by the attrChanged attribute.

When the listener’s onChange() method is called, the binder will call the InverseBindingMethod to get the value from the view, and subsequently send it back to the object.

Now if done correctly the object would be updated with the new value. The end. Not!

What happens if the value in the object changes? Nothing will happen actually. For something to happen you need to define the object as a BaseObservable object.

BaseObserveable

You can read more about ObservableObject from the official documentation. Here is an example:

private static class User extends BaseObservable {
private String firstName;
private String lastName;
@Bindable
public String getFirstName() {
return this.firstName;
}
@Bindable
public String getLastName() {
return this.lastName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
notifyPropertyChanged(BR.firstName);
}
public void setLastName(String lastName) {
this.lastName = lastName;
notifyPropertyChanged(BR.lastName);
}
}

Basically, the Observeable object has the notifyPropertyChanged() method that notifies the binder whenever it is called. This is usually called during a set operation so as to notify any changes.

Oh my Infinite Loop!

Watch out for infinite loop if you are two way binding with an Observable object. If you bind the object to an event exclusively fired by user interaction like onClick, the operation is unlikley to cause infinite loop because the user interaction is required to update the object. The whole operation will be like this:

OBJECT.get → VIEW.set → VIEW.onClick → attrChanged.onChange → VIEW.get → OBJECT.set → OBJECT.notifyPropertyChanged → OBJECT.get → VIEW.set

However, if you bind it to an event that is fired by the value changing like an OnChange event or a spinner’s OnItemSelected event, you will likely cause an infinite loop. Sometimes this won’t crash the application, but will cause some weird issues due to the system working over time. This makes it hard to debug. So if your application is hanging or is slow to respond, check for infinite loop in your binding code. Imagine this:

OBJECT.get → VIEW.set → VIEW.onChange → attrChanged.onChange → VIEW.get → OBJECT.set → OBJECT.notifyPropertyChanged → OBJECT.get → VIEW.set → VIEW.onChange → attrChanged.onChange → VIEW.get → OBJECT.set → OBJECT.notifyPropertyChanged → …

See when the value in the view changes, it will continually fire off the event, repeating the process indefinitely.

Make sure to exit the loop by checking that the value isn’t the exact same one each time you apply it to either the object or the view. Like this:

OBJECT.get → If (oldValue != newValue) { VIEW.set } → VIEW.onChange → attrChanged.onChange → VIEW.get → OBJECT.set → OBJECT.notifyPropertyChanged

Or this:

OBJECT.get → VIEW.set → VIEW.onChange → attrChanged.onChange → VIEW.get → OBJECT.set → If (oldValue != newValue) {OBJECT.notifyPropertyChanged}

That pretty much sums it up. Hopefully this has cleared some of the mystery behind data binding and inverse data binding. It was really hard to understand at first, but when you understand it, it’s quite simple, and will make your coding that much faster.

Please leave a like or comment if you find this useful. It can help others find it easily to help them understand the concept of data binding.

If you want to dive even deeper I recommend reading this post which goes into great details on what the generated binding code means.

Hello World!

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade