Android Data-Binding
Introduction
If you’re a client developer, you’re probably heard about data binding.
It’s one of the most core concepts used in many known frameworks (such as angular.js , C# WPF, Swift ReactiveKit...)
So what’s all the fuss about ?
In simple terms, data binding is the process that synchronizes (binds) elements and business logic, so when the logic (data) is changed, the elements that bound to this data changes ,and vica versa ,hence ,they’re bound to each other.
It may not sound important at first, but that’s a really important principle, because it help to separate between the UI and the business logic, which is a basic principle of designing good architecture for our apps. (and used in MVVM and more design patterns).
Data binding types
If you’ve worked with xml before, you probably noticed that there is a way to cast it into object, and work only with that object, this process called unmarsalling.
And after we finished with that object , we may want cast it back to XML in the process called marshalling.
It’s possible, due to data-binding mechanism that binds xml data elements to the relevant class member, so we can access the element via the data, and when we change the data, it changes the xml element binds to it.
That’s called XML Data Binding.
Another binding type is UI Data Binding, that binds data to UI objects (text, editable texts…) , so we can control the UI element via the data, and vica versa.
Today , its more frequent to use UI Binding, and it used in many modern client side frameworks.
There are 2 ways to do binding :
One-Way Binding
One-Way binding happens when you change the source property, and in automatically changes the target property, but not the other way around.
Typical use case, can be when you want to update read-only ui field, so you want it change when the data changes, but there is no need change the data back, because the field won’t change, as it read only mode.
If you’re client developer, you probably used text change listeners, where some data is triggered on text change. It’s also sort of one-way binding.
Two-Way Binding
Two-way binding is full synchronization between source and target properties. Means that every change in the source property will reflect in the target, and vica versa.
Typical use can be in editable form/field.
E.g, we have a name text, which is editable, so we may want to sync it with the name data it represents, so every change we’ll do in the field it will reflect in the ui, and the other way around.
Eveet data-binding framework need to use binder , which is the part that creating the binding between the elements.
Android Data Binding
First introduced in Google I/O 2015 , Android have its own data binding library, which contains many features to simplify our work with data and write less boilerplate code.
Declarative Layouts
Typical way to implement data-binding in code is to declare the data where we would declare the views.
In android, the following can be a typical layout :
Name: fragment_profile.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id=”@+id/text_name”/>
</LinearLayout>
And after data-binding :
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id=”@+id/text_name”
android:text=”@{user.name}”/>
</LinearLayout>
</layout>
As you may noticed, we explicitly declared user in the layout which is the data we want to set to this view.
To complete the binding configuration , we need to use android generated binding class :
User user = ...
…
FragmentNameBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_profile, null, false);
binding.setUser(user);
And now we explicitly set the UI filed with the data.
But, as explained before , we may want to sync it, so every time the data will change, it will be reflected in the ui ,without explicitly set it from code each time.
The way to do it in android data binding library, is to use BaseObservable
and ObservableField
:
Define binding via BaseObservable
We can create class that derived from BaseObservable
, and bind it to view :
private static class User extends BaseObservable {
private String firstName;
private String lastName;
@Bindable
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name);
}
}
We need to annotate the getter of the bindable object in the @Bindable annotation.
Next, we need to call notifyPropertyChanged
in the Setter of the object, with the generated name we declared in the @Bindable getter.
notifyPropertyChanged
doing all the magic - it notifies all the relevant views attached, that the data has been changed, so it will change the view accordingly.
In order to to this, we needed to extend our model, but what if we already have other parent ?
In this case, we can use ObservableField
.
ObservableField
Define binding via ObservableField
is easier and flexible way to implement binding.
All there is need to configure, is the fields:
private static class User {
public final ObservableField<String> name = new ObservableField<>();
}
And that’s it!
Now, the use of BaseObservable
/ ObservableField
with the binding set method, can provide us binding.
But, the binding if we change the data but what if we change the view ?
Two-Way binding
Until recently, there wasn’t simple way to implement two way binding, so the developers needed to manually write that part. (e.g use TextWatcher
).
But now there is very simple way to do it, actually no more that 1 char of difference:
In the layout, instead of this :
@{user.name}
We use this :
@={user.name}
If we add ‘=’ it will support two way binding. Simple as that !
More cool features of the android data binding library worth to know
When we get the binding class using DataBindingUtil
, it automatically generate reference to the view ids, and we can access it using camel case view id from that class.Which means that we now have global reference to every view id, and we no longer need to use findViewById
(or ButterKnife)
It’s possible to use event binding in addition to data. (e.g, listener binding)
It’s possible to use data binding with RecyclerView
using setVariable
and executePendingBindings
:
private class BindingViewHolder extends RecyclerView.ViewHolder {
public BindingViewHolder(View itemView) {
super(itemView);
}
public ViewDataBinding getBinding() {
return DataBindingUtil.getBinding(itemView);
}
}
@Override
public void onBindViewHolder(BindingViewHolder holder, int position) {
SimpleUser user = users[position];
holder.getBinding().setVariable(BR.user, user);
holder.getBinding().executePendingBindings();
}
One really cool feature is @BindingAdapter, via this we can actually binding whole function to a view via custom attribute. A typical use case can be load image from web , and then bind that to ImageView
, so we can create attribute imageUrl
and then use in the view simple as :
app:imageUrl="@{imageUrl}"
Example usage :
@BindingAdapter({"bind:imageUrl"})
public static void loadImage(ImageView view, String url) {
Picasso.with(
view.getContext()).
load(url).
into(view);
}
Conclusion
Data binding is must-to-know concept that save a lot of boilerplate code, help to separate logic from the ui and simplify work with data in general.
Android data binding library make good use of it and adding more features to make our life easier.
You can find more info on the official page.