Virtually every application works with some form of data. This data can be fetched from the Internet, a database on the target device, or even created by the application user. Typically, developers need to assign this data to the UI elements that present this data to the user. For Android apps, this is normally achieved by inflating the Activity (or Fragment) layout, locating the target UI element using findViewById, and finally assigning the appropriate data value to the element.
At Google I/O 2015, the new data binding support library was demonstrated, which can help developers perform all the above steps seamlessly using layouts (and properly defined classes and variables) only.
For this tutorial, we are going to delve into some of the features of the data binding library, and show how much more efficient and easy it can make android app development.
Getting Ready
The data binding library is a support library, and is available for android platforms from Android 2.1 (API 7) and newer. To use this library in your app, you must download the support repository using the SDK manager, and add the dataBinding element to your app build.gradle file, as shown in the snippet below
android { compileSdkVersion 24 buildToolsVersion "24.0.0" dataBinding.enabled = true ... }
The sample app built for this tutorial is made up of three Activity classes, with each one using increasingly more complex uses of the data binding features.
Data Binding Layout
Data binding layout files must be configured slightly differently from default layout files. There are a couple of files that could be generated automatically, and if the project doesn’t use data binding, the files would be needlessly generated. The power of this is that in an app, some layout files could use data binding, and have the auto generated classes, while others do not use data binding, and have no auto generated classes.
All layout files that intend to use data binding techniques must have a layout root tag. For a basic MainActivity class, a simple activity_main.xml layout would be something like this:
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://ift.tt/GYQbrm; xmlns:tools="http://ift.tt/10zyHil;> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="com.sample.foo.databindingsample.MainActivity"> <Button android:id="@+id/updateButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="@dimen/activity_horizontal_margin" android:text="@string/button1"/> <Button android:id="@+id/nextActivityButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="@dimen/activity_vertical_margin" android:layout_alignParentBottom="true" android:layout_alignParentRight="true" android:text="@string/next_activity"/> <TextView android:id="@+id/text_view1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toRightOf="@id/nextActivityButton" android:layout_alignBaseline="@id/nextActivityButton" android:padding="@dimen/activity_horizontal_margin" android:text="@string/text1a"/> </RelativeLayout> </layout>
Normal layout files begin by declaring the target root View, however, to declare a layout that supports data binding, the root tag is the layout tag. The actual UI View (in this case a RelativeLayout) is defined within the layout tag.
The layout tag is a special tag, that simply indicates to the build system that this layout file should be processed for data binding. Note that any layout file in your application without the layout root tag will not be processed for data binding.
Data Binding Activity
At the moment, we have a layout file that is data binding capable. However, to utilize its data binding ability, we have to load it in a different way.
Previously, you would load your layout like this:
setContentView(R.layout.activity_main); final Button button1 = (Button)findViewById(R.id.button1); button.setOnClickListener(...);
With data binding, a Binding class is auto generated from your layout file. The class is named using your layout file name by default. The default name is generated by capitalizing the first letter of each word after an underscore, removing all underscores, and adding ‘Binding’ to the name. As such, activity_main.xml will result in a class called ActivityMainBinding.
To associate this auto generated binding class in your code, you invoke DataBindingUtil’s setContentView
final ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView( this, R.layout.activity_main); activityMainBinding.updateButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { activityMainBinding.textView1.setText(R.string.text1b); } });
In the code snippet above, you will notice that we can access the updateButton Button directly. All views with an ‘@+id’ in a data binding layout are automatically assigned to a final field of the correct type. So Button updateButton is created for the layout Button with ‘@+id/updateButton’, and TextView textView1 is created for the id/text_view1 TextView.
That’s it. No more findViewById, and no more type casting returned views. Also, using data binding results in faster code. This is because findViewById traverses the view hierarchy every time it’s called, looking for the specified view. With data binding however, the entire layout is traversed a single time, and all relevant widgets and components are assigned to fields.
Note also the change in variable name. Each variable name is camel cased, and the underscores striped. So text_view1 becomes textView1.
Binding Objects
While the ability to work without findViewById is a bonus, and the faster code is also nice, the real power of data binding becomes apparent when you begin to bind objects. Which brings us to the second activity.
Assume you have a User object. Your activity has TextViews that display the properties of the current User object, such as the firstname, lastname, etc. To achieve this, you would use findViewById in your activity, and then use setText on each field for each corresponding TextView.
With data binding, we can bind the User object to the layout file, and then assign the appropriate user fields right from the layout file.
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://ift.tt/GYQbrm; xmlns:tools="http://ift.tt/10zyHil; tools:context="com.sample.foo.databindingsample.SecondActivity"> <data> <variable name="user" type="com.sample.foo.databindingsample.User" /> </data> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin"> <TextView android:id="@+id/firstnameLabel" android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="@dimen/activity_horizontal_margin" android:text="@string/firstname" /> <TextView android:id="@+id/firstnameTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBaseline="@id/firstnameLabel" android:layout_toRightOf="@id/firstnameLabel" android:padding="@dimen/activity_horizontal_margin" android:text="@{user.firstname}" /> <TextView android:id="@+id/lastnameLabel" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/firstnameLabel" android:padding="@dimen/activity_horizontal_margin" android:text="@string/lastname" /> <TextView android:id="@+id/lastnameTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBaseline="@id/lastnameLabel" android:layout_toRightOf="@id/lastnameLabel" android:padding="@dimen/activity_horizontal_margin" android:text="@{user.lastname}" /> </RelativeLayout> </layout>
Within the layout tag, we added a data tag before the UI view root. This data element can have variable’s within it that describes a property that can be used within the layout. There can be as many variable elements within the layout data, as necessary.
In the layout above, you can see that we set the text of two TextViews using string constants (@string/firstname and @string/lastname), while the other two TextViews have their text set using the data binding “@{}” syntax (@{user.firstname} and @{user.lastname}).
The Data Object
Amazingly, the data objects that can be used for data binding do not really need to be a special type. The target object (in this case User) can be a plain old Java object
public class User { public String firstname; public String lastname; public int age; public String gender; public User(String firstname, String lastname, int age, String gender){ this.firstname = firstname; this.lastname = lastname; this.age = age; this.gender = gender; } }
or it can be a JavaBeans object
public class User { private String firstname; private String lastname; private int age; private String gender; public User(String firstname, String lastname, int age, String gender){ this.firstname = firstname; this.lastname = lastname; this.age = age; this.gender = gender; } public String getFirstName() { return this.firstName; } public String getLastName() { return this.lastName; } public int getAge() { return this.age; } public String getGender() { return this.gender; } }
As far as the data binding library is concerned, the above classes are the same. The @{user.firstname} expression that’s evaluated for the above android:text attribute accesses the public firstname field for the plain old Java object above, or the getFirstname() method in the JavaBeans class.
To bind the User object in an activity, a method is automatically generated in your Binding class (set[VariableName]). In our sample, the layout data variable is named ‘user’, and so the method setUser() is automatically generated. The following demonstrates how to create and bind a User object in the Activity. (Note that the layout file in this case is called activity_second.xml)
final ActivitySecondBinding secondBinding = DataBindingUtil.setContentView( this, R.layout.activity_second); User myUser = new User("Android", "Authority", 22, "Corporate body"); secondBinding.setUser(myUser);
And that is all. Run the application at this point, and you’ll find that the firstname is set to Android, and the lastname to Authority.
Binding Integers
Recall that our User object has an age property that is an int. We know that TextView’s setText doesn’t accept Integers. So how do we display the int in a TextView? By using the String.valueOf() method.
<TextView android:id="@+id/ageTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toRightOf="@id/ageLabel" android:layout_alignBaseline="@id/ageLabel" android:padding="@dimen/activity_horizontal_margin" android:text="@{String.valueOf(user.age)}"/>
Yes. Go ahead and try it. And let it sink in that you are actually using a Java static method call in your xml layout file.
Imports
The above static method call magic is possible because, with the data binding library, you can actually import classes to your layout, just like in Java, and the java.lang.* package is imported automatically. Imported classes can be referenced within your layout file, for example
<data> <import type="android.view.View"/> </data> ... <TextView android:text="@{user.lastName}" android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
As in the example above, where we called the String.valueOf method, static methods and static fields can be used in expressions.
Another example of a really cool use of imports:
<data> <import type="com.example.User"/> <import type="java.util.List"/> <variable name="user" type="User"/> <variable name="userList" type="List<User>"/> </data>
Data binding expressions
The expressions used for data binding are very identical to Java expressions. Some of the Java expressions available include
- Mathematical (+ – / * %)
- String concatenation (+)
- Logical (&& ||)
- Binary (& | ^)
- Unary (+ – ! ~)
- Comparison (== > = > >>> <<)
- instanceof
Another very interesting and useful operator is the null coalescing operator (??), which evaluates to the left operand if it isn’t null, or the right if the left is null.
android:text="@{user.displayname ?? user.firstname}"
Updating Data binding objects
It’s all well and good that we can easily display objects using data binding, including Lists and Maps, and virtually any other object available to our application. However, what happens if we want to update these objects. How do updates to the bound object reflect in the UI.
If you run the Activity samples above, you will notice that if you update the bound objects, the UI doesn’t update as well. To unlock the full power of data binding, you will want to update the UI automatically, in response to changes to the bound object.
ObservableFields
The easiest way to achieve this, is to use an ObservableField for properties that can change.
public class User { public final ObservableField<String> firstname = new ObservableField<>(); public final ObservableField<String> lastname = new ObservableField<>(); public final ObservableField<Integer> age = new ObservableField<>(); public final ObservableField<String> gender = new ObservableField<>();
Rather than accessing the values directly, you use the set age get accessor methods provided by ObservableField:
user.firstName.set("Google"); int age = user.age.get();
Observable Objects
Another way to achieve data change notifications involves the use of Observable objects. These are objects that either implement the Observable interface, or extend the BaseObservable class. In our sample code, we implement an Observable object as shown below. In each setter method, we called the notifyPropertyChanged method, and for each getter, we added the @Bindable annotation.
private static class User extends BaseObservable { private String firstName; private String lastName; @Bindable public String getFirstName() { return this.firstName; } @Bindable public String getLastName() { return this.lastName; } public void setFirstName(String firstName) { this.firstName = firstName; notifyPropertyChanged(BR.firstName); } public void setLastName(String lastName) { this.lastName = lastName; notifyPropertyChanged(BR.lastName); } }
Event Handling
Using data binding, you can also handle events right from the layout xml using either Method references, or Listener bindings. For the sample application, we implemented event handling using the method references technique. Your target method must conform to the signature of the listener method, while data binding performs the magic of wrapping your method reference and the owner in a listener and setting the listener on the target view.
For example, we create a class which we named ThirdActivityHandler, with a simple method called onClickButton to handle button clicks. On each click, we call getTag on the button to know how many times it has been clicked, increment by 1, display the current number of clicks on the button and call setTag to set the new number of clicks.
public class ThirdActivityHandler { public void onClickButton(View view) { if(view instanceof Button){ int times = Integer.parseInt(view.getTag().toString()); times += 1; ((Button) view).setText("Clicked " + times + " times"); view.setTag(times); } } }
In the layout file, we declare our ThirdActivityHandler variable, and set the Button android:onClick using “@{buttonHandler::onClickButton}”.
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://ift.tt/GYQbrm; xmlns:tools="http://ift.tt/10zyHil; tools:context="com.sample.foo.databindingsample.ThirdActivity"> <data> <variable name="user" type="com.sample.foo.databindingsample.ObservableUser"/> <variable name="buttonHandler" type="com.sample.foo.databindingsample.ThirdActivityHandler"/> </data> <RelativeLayout> ... <Button android:id="@+id/nextActivityButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_alignParentRight="true" android:text="@string/clicked_count" android:tag="0" android:onClick="@{buttonHandler::onClickButton}"/> </RelativeLayout> </layout>
Conclusion
We have barely scratched the surface of the capabilities of data binding in this tutorial. For a more in depth and longer discussion, check out the data binding android developer article. Using data binding can lead to faster development times, faster execution times and easier to read (and maintain) code.
The complete source for the app developed during this tutorial is available on github. We would love to hear some of your favorite ways to use the new library and/or questions on implementation. Happy coding.
via Android Authority http://ift.tt/2aYAxUr