Building Android Apps with MVVM and Data Binding
Great Design Pattern for Great Work
Most app development cannot avoid being composed of three parts: database/model, UI and the business logic associated with them. There are already many design patterns that allow for more structured communications between model and UI; for example, MVP and MVC.
The goal indeed, is not just to link one to the other; but to have a fully organized and modular layer that can act as a bridge and handle different kinds of jobs. Only when that layer is modularized can it be easily maintained, tested, grown and flexibly changed in the future.
For example, a simple product assembly line: making -> wrapping -> shipping. Individuals who do the making don’t affect the ones who do the shipping. This means it’s encapsulated. If you want to send out your product to another country, you just need to add an extra car or plane. And that’s what flexibility and encapsulation allows us to do.
Data Binding Makes It Better
I believe that you already know about the merits of having multiple modules to help communicate between the UI and database. What’s the demerit? Respecting the clean architecture principle, modules communicate using interfaces, and so the more modules you have, the more interfaces you need to create.
Fortunately Android developers have some good new tools from Google. Google released the Data Binding library in May 2015, and it is the hero that can save the interfacing between the XML layouts and the Java classes. It controls and consumes elements from the layout. And these elements will hold the data from the database/model that should be provided.
In Android, it’s common to use findViewById in Java files to get the references to the layout elements. However, after plugging in the Data Binding library, each data change is reflected automatically in the bound layout elements. So we can get rid of findViewById and also the getter and setters in the Model. This saves us a lot of work, and makes it easier for us to concentrate on the business logic.
It’s a super awesome library and I want to demonstrate that in a TODO app. Source code
Let’s code!
Adding Data Binding to The Project
First of all, make sure you’re running Android Studio 1.3 or higher. If your install is up to date, you don’t have to worry about it.
Next edit the project’s build.gradle file, the android section looks like this:
[code language=”java”]
android {
…
dataBinding {
enabled = true;
}
}
[/code]
That’s it! All you need to do is this in the build.gradle.
Using Model Without Getter and Setter
In the app, I have only one page that includes a list of items. Each item is composed of an event description and an event state. So I built a model that has those “event” and ”state” members.
Create a model class for data binding.
[code language=”java”]
public class TodoItem extends BaseObservable{
public final ObservableField<String> state = new ObservableField<>();
public final ObservableField<String> event = new ObservableField<>();
}
[/code]
Note that the model class should extend BaseObservable. BaseObservable provides the infrastructure for setting up the data binding; the POJO can notify registered listeners as values change.
With the code set up in Java, let’s move to the XML layout now.
Modifying the layouts
Data-bound layout files are slightly different and they start with a root tag of <layout> followed by a <data> element and a root view element. This view element is what your root would be in a non-binding layout file. My XML file for the Recycler view’s row item is attached below:
[code language=”xml”]
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable name="todoItem" type="com.example.jessicazeng.mvvm_todo_list.model.TodoItem"/>
</data>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<TextView
android:id="@+id/todo_event"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
tools:text="Test Data"
android:text="@{todoItem.event}"
android:layout_alignParentStart="true"
android:layout_marginStart="20dp"/>
<TextView
android:id="@+id/todo_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
tools:text="Test Data"
android:text="@{todoItem.state}"
android:textColor="@color/colorAccent"
android:layout_alignParentEnd="true"
android:layout_marginEnd="20dp"/>
</RelativeLayout>
</layout>
[/code]
As you can see, the variable type is the model class path of it.
[code language=”xml”]
<variable name="todoItem" type="com.example.jessicazeng.mvvm_todo_list.model.TodoItem"/>
[/code]
We use the “@{…}” syntax to bind data with a specific field. In the first text view, I set the text as the event property of the TodoItem.
[code language=”xml”]
android:text="@{todoItem.event}"
[/code]
Similar to the one above, I set the second TextView text as the state property.
[code language=”xml”]
android:text="@{todoItem.state}"
[/code]
With this in place, the last thing we need to do is to establish the data binding in the activity, fragment or anyplace we need to link it to the layout.
Establishing the Data Binding
In Fragment
Even though we are no longer using findViewById to get a view reference, we still need the interface that connects the layout and the fragment. And here’s what we need:
[code language=”java”]
TodoListViewBinding binding = DataBindingUtil.setContentView(getActivity(), R.layout.todo_list_view);
[/code]
The Binding class will be generated by default, based on the name of the layout file, converted to Pascal case and suffixing “Binding” to it. The todo_list_view.xml is generated into TodoListViewBinding.
Now we are bound to the layout, and we can get a reference to the view elements like this:
[code language=”java”]
binding.recyclerView.setLayoutManager(LayoutManager);
binding.recyclerView.setAdapter(adapter);
[/code]
As you can see I am using adapter along with the recyclerView. We also need to do the data binding in the adapter.
In Adapter
Same as in the fragment, we need a binding class here. The only difference is that we need to initialize the binding class like this instead:
[code language=”java”]
TodoListRowBinding binding = DataBindingUtil.bind(itemView);
[/code]
And additionally, we want to change the event state using single click and remove the event by using long click. Here is the code:
[code language=”java”]
public class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(View itemView) {
super(itemView);
binding = DataBindingUtil.bind(itemView);
}
public void takeItem(final TodoItem item) {
binding.setVariable(BR.todoItem, item);
binding.executePendingBindings();
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (item.state.get().equals(context.getString(R.string.undo_state))) {
item.state.set(context.getString(R.string.done_state));
} else {
item.state.set(context.getString(R.string.undo_state));
}
}
});
itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
todoLists.remove(getAdapterPosition());
notifyItemRemoved(getAdapterPosition());
return false;
}
});
}
}
[/code]
There are some things to point out. First you can see that even though we didn’t have a getter and setter in the model, the data binding library created them for us.
And also, a BR class (for Bound Resources) was generated by the data binding library.
[code language=”java”]
binding.setVariable(BR.todoItem, item);
[/code]
It is to Data Binding what the R class is to layout files. At compile time, the layout <data> <variable name=””> will be added to the BR class as a constant.
Conclusion
Here were some of the major parts of code for the TODO app; they show how we implemented data binding in the project. Let’s review the steps, firstly we implemented the Data Binding library in build.gradle. Secondly, we created a Model class (don’t forget to extend the BaseObservable). Next, <data> and <variable> fields were added to the layout, and the compiler took care of generating a BR.constant matching the name of that variable. Last, we needed to access the layout elements within the java class. A TodoListRowBinding class was generated to do the interfacing. That’s basically all we needed todo.
In the end, we saved a lot of work in the writing of the interfacing between View and View Controller, and in the writing of the getters and setters in the Model. I was very impressed by how much code was automatically generated. We not only saved a lot of time, but also reduced the chances of making mistakes. And I am sure that there are more magical things we can do with it. For more information, you might be interested in a deeper dive @ http://developer.android.com/tools/data-binding/guide.html.
2 Comments