The latest version of Android came out on the market some time ago and developers got a lot of its technical features to talk and learn about. We would also like to touch upon the topic.

Android Studio 1.3 has brought the Data Binding Library to make our lives easier. It allows developers writing declarative layouts and a better glue code to bind them with application logic. Minimal efforts, but better results. The library can be used starting from Android 2.1+ versions.

So, how can data binding be used? The answer is below. Let’s have a look.

Enable Data Binding in Your Project

Currently you don’t have to add this library as dependency. The only thing you need is to enable it in your build.gradle app module inside android section:

android {
dataBinding {
enabled = true
}
}
view raw databinding1 hosted with ❤ by GitHub

You need to add this in build.gradle for each module where you want to use it.

Goodbye FindViewById

With the data binding library you don’t have to write findViewById lines anymore. There are third party libraries that can help you with that, but this is inbox solution. Let’s find out how it works.

First you need to create a layout with this template:

<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<!-- Variables and imports goes here -->
</data>
<!-- Your layout goes here -->
</layout>

Tags <layout> and <data> are required. The <data> tag can be empty. We need at least one view with “android:id” for the data binding plugin to generate a class for this layout.

Let’s create a sample layout file - activity_main.xml.

<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data />
<TextView android:id=”@+id/tv_title”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”/>
</layout>
view raw activity_main.xml hosted with ❤ by GitHub

After you save this layout, data binding will generate a class for it. The binding class path:

<your package name>.databinding.<Camel case layout name>Binding

For example, if a package name is “com.test.sample” and a layout file name is “activity_main.xml” then the class will be :

com.test.sample.databinding.ActivityMainBinding

This class will store all layout views with id as fields with name=LowerCamelCase(id). For instance,tv_title = tvTitle.

Now you can use the DataBindingUtil class to get a binding layout object. There is a possibility to do it in several ways, depending on where you use this layout.

If you use the layout in Activity you can apply the following method in DataBindingUtil:

<T extends ViewDataBinding> T setContentView(Activity activity, int layoutId)

This will set Activity content view to the given layout and return the associated binding. The given layout resource must not be a merge layout.

Activity sample code to bind and access view from binding object:

private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
//tvTitle = TextView in layout with id = tv_title
binding.tvTitle.setText("Hello world!");
}

If you use the layout in fragments you can apply the following static method from DataBindingUtil:

inflate (LayoutInflater inflater, int layoutId, ViewGroup parent, boolean attachToParent)

or you can get binding from any view for all cases:

ActivityDataBinding binding = DataBindingUtil.bind(view);

DataBinding also works with <include> layouts. You just need to add “android:id” to include tag and then you can get included views in the following way:

binding.<included id>.<view in included layout id>

MVVM and Data Binding

MVVM is an application pattern that provides a clean separation between UI and Logic. It improves application testability and allows UI and Logic to evolve independently.

There are such parts of MVVM pattern as:

  • The View - your application’s UI. It displays information to a user and can fire user events.
  • The Model - business logic and data of an application.

  • The ViewModel - a bridge between the View and the Model. It handles all UI and model change events, represents the Model to UI, updates the Model with user input.

This is how the relationship between the View, the ViewModel, and the Model looks like:

Interaction Between View, ViewModel, and Model
Interaction Between View, ViewModel, and Model

The Relationship between the ViewModel and the Model (Model change events, Update, Read) is not related to the Data Binding Library so we don’t focus on this. It can be implemented in a lot of ways.

Let’s focus on the View-ViewModel relation implementation with the Data Binding Library (ViewModel data, UI events, PropertyChanged events).

  • "ViewModel data relation" is the way how the View can get data from the ViewModel.

First you need to create the ViewModel class.

public class PlaceItemViewModel {
private Context context;
private PlaceModel placeModel;
public PlaceItemViewModel(Context context, PlaceModel placeModel) {
this.context = context;
this.placeModel = placeModel;
}
public String getPlaceTitle() {
return placeModel.getTitle();
}
public String getCountVotes() {
//just stubbed value
return 10;
}
}

Then show two values from this class getters in UI: getPlaceTitle(), getCountVotes().

In order to bind Java Data Object to the XML layout include it as a variable in the <data> tag:

<data>
<variable name="your sample name" type="com.sample.SampleClassName"/>
</data>
view raw SampleClassName hosted with ❤ by GitHub

Name - a variable name that will be used in the XML layout to get this object and in the generated binding class with getters and setters.

Type - a full package classname.

Create the item layout item_place.xml:

<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="viewModel"
type="com.test.sample.viewmodel.PlaceItemViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tv_title"
style="@style/TitleLeft"
android:text="@{viewModel.placeTitle}" />
<TextView
android:id="@+id/tv_votes_count"
style="@style/Subtitle"
android:text="@{viewModel.countVotes}" />
</LinearLayout>
</layout>
view raw item_place.xml hosted with ❤ by GitHub

Field relation by name works on the get method names too:

"@{{'{'}} viewModel.placeTitle {{'}'}}" = getPlaceTitle()
"@{{'{'}} viewModel.countVotes {{'}'}}" = getCountVotes()

Now you can easily set the ViewModel to the View by calling the setViewModel generated method in the ItemPlaceBinding class.

An example of this data binding class usage in the RecyclerView adapter:

public class PlacesAdapter extends RecyclerView.Adapter<PlacesAdapter.BindingHolder> {
private List<PlacesResponseModel.PlaceModel> places;
private Activity activity;
public PlacesAdapter(Activity activity, List<PlaceModel> places) {
this.activity = activity;
this.places = places;
}
@Override
public BindingHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new BindingHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_place, parent, false));
}
@Override
public void onBindViewHolder(BindingHolder holder, int position) {
ItemPlaceBinding placeBinding = holder.getBinding();
placeBinding.setViewModel(new PlaceItemViewModel(activity, places.get(position)));
holder.getBinding().executePendingBindings();
}
public static class BindingHolder extends RecyclerView.ViewHolder {
private ItemPlaceBinding binding;
public BindingHolder(View rowView) {
super(rowView);
binding = DataBindingUtil.bind(rowView);
}
public ItemPlaceBinding getBinding() {
return binding;
}
}
}
view raw PlacesAdapter.java hosted with ❤ by GitHub

executePendingBindings() will force binding to be executed immediately. In other cases binding will be scheduled to change before the next frame.

  • ‘PropetryChanged events’ is the way how the ViewModel can notify the View about data changes.

You can extend BaseObservable in your ViewModel and notify about updates using the notifyChange() methods. One more option is to use ObservableField:

public final ObservableField<String> countVotes = new ObservableField<>();
view raw ObservableField hosted with ❤ by GitHub

countVotes.set("Some text") will update the related View with {{ "@{viewModel.countVotes}" }}

Using the Expression Language in XML you can do a lot of useful things, e.g., this will show/hide the View depending on the isDataLoading value:

View

android:visibility="@{viewModel.isDataLoading ? android.view.View.VISIBLE : android.view.View.GONE}"
view raw Markup.xml hosted with ❤ by GitHub

ViewModel

public final ObservableField<Boolean> isDataLoading = new ObservableField<>();
  • ‘UI Events’ is the way how the View can notify the ViewModel about User Interaction.

Let’s add a click listener to TextView:

<TextView
style="@style/Subtitle"
android:text="@{viewModel.title}"
android:onClick="@{viewModel.titleClicked}"/>
view raw TitleClicked hosted with ❤ by GitHub

The onVoteClicked method in the ViewModel will be called when a user clicks on TextView.

Here is an example of the ViewModel:

public class TitleItemViewModel {
public final ObservableField<String> title = new ObservableField<>();
public TitleItemViewModel() {
title.set("Some title");
}
public void titleClicked(View view) {
title.set("Title clicked");
}
}

At first the value of TextView will be “Some title” and after a user clicks on the view, the value will change to “Title clicked” and the user will see it in UI right away.

Converters

Converters allow us to write custom code to convert the value from one type to another.

A common issue is to display the Date in UI, so you need to convert it to String View:

<TextView
style="@style/Subtitle"
android:text="@{viewModel.updatedDate}"/>
view raw DateTextView hosted with ❤ by GitHub

The ViewModel:

public final ObservableField<Date> updatedDate = new ObservableField<>();
view raw DateTextField.java hosted with ❤ by GitHub

Now display the updated Date in TextView. The only thing you have to do to make the above code work is to write the converter method:

@BindingConversion
public static String convertDateToDisplayedText(Date date) {
return new SimpleDateFormat("yyyy:MM:dd").format(date);
}

This method can be in any class inside of your module and must be public static with the BindingConversion annotation. As for a method signature, all that matters is to return and input a data type. You can use any method name you like.

If you need different conversion code for the same types you can create static methods and call them using the Expression Language:

<code>
android:text=
"@{DateConverter.convertDateToDisplayTime(viewModel.updatedDate)}"/>
</code>
view raw Converter.xml hosted with ❤ by GitHub

You need to remember that conversion methods run on the UI thread and shouldn’t do any blocking tasks. You can use Binding adapters for async operations.

Binding Adapters

Binding adapters are useful for setter customization. For example, a custom loader can be called off-thread to load an image.

Custom binding adapters will override the data binding default adapters when there is a conflict.

The adapter method must be a public static method in any class in the module. And the annotation is BindingAdapter.

This is how you can use the adapter method to load an image from string URL:

@BindingAdapter({"bind:imageUrl"})
public static void loadImage(ImageView imageView, String imageUrl) {
Glide.with(imageView.getContext())
.load(imageUrl)
.diskCacheStrategy(DiskCacheStrategy.SOURCE)
.placeholder(R.color.lighter_gray)
.into(imageView);
view raw ImageUrl.java hosted with ❤ by GitHub

Now you can load the image from the string URL using the following method from XML:

<ImageView
android:layout_width="@dimen/place_item_image_size"
android:layout_height="@dimen/place_item_image_size"
app:imageUrl="@{viewModel.imageUrl}" />
view raw ImageUrl.xml hosted with ❤ by GitHub

Relation to the method : {{ app:imageUrl = {"bind:imageUrl"} }}

One more example of using the binding adapter method is setting a custom font to TextView.

@BindingAdapter({"bind:font"})
public static void setFont(TextView textView, String fontName) {
Typeface typeface = Typeface.createFromAsset(context.getAssets(), "fonts/" + fontName);
textView.setTypeface(typeface);
}
view raw FontBinding.java hosted with ❤ by GitHub

Then you need to set the text font to fontName from the assets/fonts folder.

<TextView
style="@style/QuoteMainTextStyle"
app:font="@{@string/app_font}" />
view raw TextView.xml hosted with ❤ by GitHub

After that add a font string to resources:

<string name="app_font">San-Francisco-Regular.ttf</string>
view raw StringResource.xml hosted with ❤ by GitHub

You can even localize a font with strings.xml in cases where different fonts are required for different languages.

Conclusion

Data binding can be used in 3 ways:

  1. Use it as FindViewById helper library.
  2. Use 2-way binding to implement MVVM pattern in your apps.
  3. Invoke your Java code from XML (Converters, Binding Adapters).

Considering the first point, there are similar third party libraries, such as ButterKnife, RxBinding etc. But we finally have inbox solution for this.

As for MVVM, there is a framework for Android called RoboBinding that can also be used. Yet current realization from Google seems to be much better. Moreover, it is predicted to improve in Android Studio 2.0 release. The most desired feature in this regard is autocomplete for data binding expressions in XML.

Speaking about Binding Adapters and Converters, it’s always good to move out some code logic from UI controllers. Setting fonts, loading images, animating views are just small examples of features you can implement in XML with helper methods. You can also put all these methods together and make your own extension library. The ability to produce some declarative code is always interesting on all platforms.

Want to learn more about our Android development services?

Get in touch with us to discuss your Android project and learn how our team can assist you.

Contact us