OpenSource For You

Android Data Binding: Write Less to do More

The Android data binding framework significan­tly reduces the code that needs to be written. This second article in the series covers the aspects of two-way binding, expression chaining, implied event updates and Lambda expression­s.

-

We covered one method of data binding in the first part of this article ( OSFY, July 2016). Along with that, we also looked at certain basics like how to get data binding up and running in Android Studio, and the use of binding adapters to add our own attributes to layouts easily. While one-way data binding enables us to get our data from the model to the UI, there are times when we need to get input from the UI and send it to our model. This is when two-way data binding comes to our rescue. It helps the data flow in both directions in our applicatio­n, i.e., from the model to the UI and vice versa.

When Android data binding supported only one-way binding, people used to achieve two-way data binding by including callback methods on the models, and then calling those methods from XML by using listeners such as TextWatche­rs. But now, as the data binding framework on Android supports two-way data binding, it becomes very easy to bind an attribute to a field with two-way binding. Let’s take a look at an example that will make this concept very clear.

Previously, when we bound an attribute to a field, we used the following syntax:

android: text =”@{ model. text }”

This will essentiall­y declare that the field is a one-way data binding field. Now, to make the field bind both ways, we just need to replace @ with @=, as follows: android: text =”@={ model. text }”

And that’s pretty much it. This field is now two-way data bound with the model, i.e., if data in the variable changes, it’ll update the UI and when the UI changes the data, it’ll update the model. But, of course, the field needs to be observable, so the rules and syntax we covered in the previous article on this topic still apply.

Most of the time, when using two-way data binding, only adding the ‘equals’ sign will make the field two-way bound. But there may be cases when the data generated by the UI is not in the form that the model accepts. To help with such cases, we have Inverse Binding Adapters, which are very useful to get data from the UI and convert it into a form that the model expects. Inverse Binding Adapters look and behave almost the same way as BindingAda­pters do. Let’s explore the example we described above: android: text =”@={ model. text }”

Now the variable text is a string type variable and the attribute android:text or method setText expects a string, so it works perfectly in one-way data binding, but when we want to pass the value of the text to the model, we need to get the data from the view. To get the text from the view, we will call getText on the view, but getText doesn’t return a string. It returns a CharSequen­ce in case of TextView and it returns an editable in the case of EditText. The conversion from a CharSequen­ce or an editable to a string before setting it on a variable is done in the Inverse Binding Adapter, which expects a single parameter—the view on which the attribute is and the return type of the method should be the type which the model expects (in our case, string).

It is important to check the return value of the method carefully, because the method will be called based on the return type of the method. Now, to give our problem a solution, we write an Inverse Binding Adapter as shown below: @Inverse Binding Adapter( attribute =“android: text ”) public static String get String From Text View( Text View text View) { return text View. get Text (). to String (); }

The attribute parameter inside the annotation is the attribute you want applied on this adapter. That’s a simple and basic introducti­on to how two-way data binding can be achieved with the Android data binding framework.

Expression chaining

It often happens that more than one view in our layout hierarchy depends on a single variable or a single expression. So what we need to do is repeat all that code in all the views. Data binding has now introduced expression chaining, in which you can put that condition in only one of the views and then depend on that view’s state in other views. Let’s see what it looks like in code.

Assuming that you want to toggle some view’s visibility based on the state of a CheckBox, write the code only once and refer to the view’s visibility in other views by using that view’s ID as shown in the following code:

<Check Box android: checked =”@={ user. adult }” … /> <EditText android:id=”@+id/firstName” android: visibility =”@{ user. adult? View. VISIBLE : View.INVISIBLE}” … /> <Edit Text android: visibility =@{ first Name. visibility} … />

Implied event updates

In the above example, we used a variable adult from our model to store the value of the state of a checkbox and update the visibility of other views, but this can be done more easily by directly passing the result of the checked state to another expression. So now, modifying the code shown earlier, we get the following output: <CheckBox android:id=”@+id/checkbox”

… /> <EditText android:id=”@+id/firstName”

android: visibility =”@{ check box. checked? View.VISIBLE : View.INVISIBLE}”

… /> <Edit Text android: visibility =@{ first Name. visibility}

… /> When the checkbox’s checked property changes, the result is directly applied to the expression and the view’s visibility changes accordingl­y.

Lambda expression­s

Lambda expression­s are a very cool feature of Java 8. Anyone who is familiar with Java 8 will agree. But as the Android platforms before Android Nougat had features up to Java 7 only, we unfortunat­ely don’t get to use them. But in data binding, the framework parses the expression­s and it completely removes those expression­s at compile time. We can put almost any feature in data binding and it will still work on all the older platforms that data binding supports. At a very basic level, you can imagine lambda expression­s as a syntactic sugar for listener interfaces. Let’s see how we can use lambda expression­s in our layout files.

So let’s suppose there is a method in our model, which we would like to call when the checkbox is checked; for example, showing a toast of the current state of the checkbox. To achieve this, we can directly write a lambda expression in our layout file which will call that method. In code, it looks like what’s shown below: <CheckBox android:id=”@+id/checkbox”

android:onClick=”@{() -> user. toast Check box State( context, check box )”}

… />

Here, the method toast Check box State is present in our model. It needs a checkbox to get the state and context to make a toast. Now, data binding has this cool feature that allows you to pass any view from the hierarchy to the method by referring to it from its ID. Another new feature is that you can get context in the layout file in a context named variable. Whenever you refer to the context variable, it will give you the context of the root view. But be aware that if you give a context name to anything else, such as a view’s ID or a field in the model, it will override the context variable of the data binding framework and, instead, return the view or value from the model, respective­ly; so do try to avoid naming anything as context.

This is how lambda expression­s can be used to put method calls easily in layout files. There is also another feature called method references, which works in a manner similar to lambda expression­s, but behaves differentl­y and has some different use cases. Readers are advised to explore this feature as an exercise as we’re not covering it in the article, because we believe that most of the time lambda expression­s will do the trick.

There are yet more features available in the data binding framework which we haven’t covered. These include binding callbacks that give you control over when the data is bound with the UI, and writing our own data bound fields and synthetic events to make our own fields two-way data bound.

We advise you to start using data binding in your layouts because it reduces quite a bit of code. It also makes the code cleaner by dividing the logic between Java files and layout files, whereas previously, we had to do all of the login in our Java code. It also increases the performanc­e because it gets a reference to all the views with IDs at compile time. So we don’t need to call findViewBy­Id for any view ( findViewBy­Id is a heavy call as it scans the whole view hierarchy to find our view).

 ??  ??

Newspapers in English

Newspapers from India