Android Data Binding And ViewModels: In Perspective — PART 2

With Android Data Binding And ViewModels, the problems we are solving are:

1. Eliminate redundant code,

2. Increase productivity and

3. Make layout files more dynamic.

Android Data Binding and ViewModels: In Perspective — Part 1 will introduce the example that we are using to demonstrate, in tackling these problems. It will also help you get familiar with the basics of One-Way Data Binding. It is a prerequisite to this part. It is recommended you read Part 1 first.

Photo: Komal Kshatriya

In this Part-2 writeup, we are going to focus on:

  • Custom Binding Adapters
  • Observability
  • Two-Way Data Binding

Custom Binding Adapters

First, let’s understand what Binding Adapters are. They are attribute setters. They are responsible for making appropriate framework calls to set values.

For example, when

android:text="@{text}" 

is bound, the Binding Adapter makes a call to

public final void setText(Charsequence text)

But, what if you have a custom attribute?

app:totalFareText=@{ticket}

That’s where Custom Binding Adapters come into the picture.

Using Custom Binding Adapters

In fare_details.xml, we have a custom attribute app:totalFareText


<TextView
android:id="@+id/totalFareText"
...
app:totalFareText="@{ticket}" />

In TicketFragment.java, we have the following:

@BindingAdapter("totalFareText")
public static void setTotalFare(TextView view,
TicketViewModel ticketViewModel) {
Log.d(TAG, "In computeTotalFare()");
view.setText(String.valueOf(ticketViewModel.computeTotalFare()));
}

The method name can be anything reasonable that you prefer but it should be annotated with the attribute name for which you are creating the Binding Adapter. Also, the first parameter, TextView in the above case, should be of the same type as the one in layout file. The second parameter, TicketViewModel, should be of the same type as the variable you are binding to the custom attribute in the layout file.

By doing this, the framework now knows which method to call to set the view for the attribute.

Observability

To build a high-quality app, it has to be very reactive. This can be achieved by using LiveData in ViewModels. LiveData helps you ensure the app UI matches with the state of the data. So if the data related to the UI changes, the UI changes accordingly. In this example, we are using Observability for Fare Details. With the help of it, we are able to expand/collapse the Fare Details view (without creating a custom view).

<TextView
android:id="@+id/ticketFareText"
...
android:text="@{String.valueOf(ticket.ticketFare)}"
android:visibility="@{ticket.isFareExpanded ? View.VISIBLE : View.GONE}" />
<TextView
android:id="@+id/gstText"
...
android:text="@{String.valueOf(ticket.gstCharge)}"
android:visibility="@{ticket.isFareExpanded ? View.VISIBLE : View.GONE}" />

The visibility of ticketFareText and gstText depends on the isFareExpanded. If it is true, the views are made VISIBLE else GONE. Now, when the user interacts with the expand/collapse icon, toggleFare() is called.

/* Binds ExpandIcon with TRUE and sets toggle on it.    
* Without MutableLiveData the EXPAND/COLLAPSE WONT WORK */ private void bindExpandIcon() {
ticketViewModel.isFareExpanded = new MutableLiveData<>();
ticketViewModel.isFareExpanded.setValue(true); //DEFAULT VALUE
mFragBinding.imageView.setOnClickListener(v -> {
ticketViewModel.toggleFare();
});

}

public void toggleFare() {
isFareExpanded.setValue(!isFareExpanded.getValue());
}

As the data for the UI, that is isFareExpanded, changes, the UI changes accordingly.

Two-Way Data Binding

What we saw up till now was One-Way Data Binding, any changes to ViewModel data is updated to the View. Two-Way Data Binding also allows any changes to the View to update the UI data. This can be helpful when the user is interacting with views in the app.

Using Two-Way Data Binding

<android.support.constraint.ConstraintLayout
android:id="@+id/main"
...
android:background="@{ticket.nightModeChecked ?@drawable/nightMode : @drawable/lightGray}" >
<CheckBox
android:id="@+id/nightModeCheckBox"
...
android:text="Night Mode"
android:checked="@={ticket.nightModeChecked}"
...
/>

The syntax for Two-Way data binding is similar to One-Way data binding. The only difference is syntactical: “@={}”.

When the user checks the Night Mode, the nightModeChecked value (the UI data) is changed. Here, the UI is changing the data. As the data changes, the android:background changes accordingly.

This is a simple two-way data binding example.

I leave it to the readers to explore two-way data binding with custom binding adapters.

Special thanks to Mr. Raju Dodhiawala(@rajudodhia) for his ideas and advice. These articles are based on the inputs provided by him.

Source Code

You can check out the source code here: https://github.com/FireAndIce/ticket-databinding

A Technologist | Android Developer | Catch me on Twitter and LinkedIn