Android Data binding Example [Simple Tutorial]

Spread knowledge

As Android developers we all were sick of  the multiple findViewById() in our Activity/Fragments. This call was mandatory for every view which needs to be accessed in your class. This added a lot of boiler-plate code in your application making it complex and difficult to maintain. For example we have a User class and wanted a TextView in layout to display the name field of User object. Below snippet shows how we used to achieve. Problem was this had to repeated for every field which needs to be binded to UI

 TextView textView = findViewById(R.id.text_view);
textView.setText(user.name);

Finally Google decided to get rid of it using the new Android Data Binding library as a part of Android Jetpack. This library allows developers to bind UI elements directly with the data source in the XML file itself. In this article we will be discussing how this library works and how to implement it in your Android application.

It is always to easy to understand any library when we develop an application using it. Therefore let us develop a simple Android application to display user details in the launch activity using Data binding. 

Enable Android Data Binding

  • You don’t need to add any gradle dependency in your application to enable Android Data Binding. It is a part of Support Repository which you can update in SDK Tools of SDK manager. Just make sure to add the following in your build.gradle to enable Data Binding
android {
    ...
    dataBinding {
        enabled = true
    }
}

Define the Data Source

  • As already mentioned Android Data Binding enables binding data directly with the UI elements. So in order to continue first we need to define a simple Data Model. Below is a sample UserModel class with member fields like nameage, imageUri etc.
    package com.example.databindingex;
    
    import java.util.Arrays;
    import java.util.List;
    
    public class UserModel {
    
        private String name;
        private String imageUrl;
        private int age;
        private List<String> hobbies;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
    
        public String getImageUrl() {
            return imageUrl;
        }
    
        public void setImageUrl(String imageUrl) {
            this.imageUrl = imageUrl;
        }
    
        public List<String> getHobbies() {
            return hobbies;
        }
    
        public void setHobbies(List<String> hobbies) {
            this.hobbies = hobbies;
        }
    
    }
    
  • In a real world application, data for such model is fetched from server using a network request. But to simplify things in this example we are using dummy static data.
    package com.example.databindingex;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    
    public class UserModel {
    
       ...
        
        /*
        * Returns a valid UserModel object with dummy data 
        * */
    
        public static UserModel getValidStub() {
            UserModel userModel = new UserModel();
            userModel.setName("Bob Vance");
            userModel.setAge(35);
            userModel.setImageUrl("https://randomuser.me/api/portraits/men/28.jpg");
            userModel.setHobbies(Arrays.asList(new String[]{"Reading books","Travelling","Listening music"}));
            
            return userModel;
        }
        
    }
    

Adding Data source to Layout

  • In usual scenario our layout file would look something like below. It defines all the views required to display the data with RelativeLayout as its root view.  It has three TextViews to display name age, hobbies. ImageView is used for displaying image and a Button on click of which we will emulate submit action.
    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="16dp"
        tools:context=".MainActivity">
    
        <ImageView
            android:id="@+id/image_view"
            android:layout_width="120dp"
            android:layout_height="150dp"
            android:layout_marginRight="16dp"
            android:scaleType="fitXY"
            tools:src="@mipmap/ic_launcher" />
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="150dp"
            android:layout_toRightOf="@id/image_view"
            android:orientation="vertical">
    
            <TextView
                android:id="@+id/name_text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textColor="@android:color/black"
                android:textSize="18sp"
                android:textStyle="bold"
                tools:text="Rohit Kumar" />
    
            <TextView
                android:id="@+id/age_text"
                android:layout_marginTop="8dp"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textSize="14sp"
                android:textStyle="bold"
                tools:text="28" />
    
            <TextView
                android:id="@+id/hobbies_text"
                android:layout_marginTop="8dp"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textColor="@android:color/darker_gray"
                android:textSize="14sp"
                android:textStyle="bold"
                tools:text="Playing Cricket" />
        </LinearLayout>
    
        <Button
            android:id="@+id/submit_btn"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/image_view"
            android:layout_marginTop="16dp"
            android:text="SUBMIT" />
    </RelativeLayout>
  • To enable Android Data Binding in a XML file the root element should be changed to <layout>. This new <layout> root will contain the normal view hierarchy and a <data> element which defines what data this layout will be binded to.
  • Therefore for this example too we will make the necessary changes in the layout file as shown below.
    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools">
    
        <data>
            <!-- Enable us to reference View class directly  -->
            <import type="android.view.View" />
            <!-- Defines a variable user of type UserModel -->
            <variable
                name="user"
                type="com.example.databindingex.UserModel" />
        </data>
    
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:padding="16dp"
            tools:context=".MainActivity">
              
              
            ...
    
         
        </RelativeLayout>
    
    </layout>
  • As seen in the code snippet we add a variable of type UserModel with the name user. This name can now be used in expression syntax to reference the variable. I consider adding variable in layout file same as declaring a instance variable in a class. In both cases we define the variable name and type and then name is used to access the variable in the entire file.
  • You can also access any Class from your layout file using the <import> tag under the data element. This is similar to how we import classes in our Java files so that we can reference them easily. Usually <import> is used to access static fields and methods of that class.
  • In this example we import android.widget.View so that we are able to access static fields View.GONE and View.VISIBLE.

Binding data variable to layout elements

Expression syntax is used to bind value to any layout element. @{} inside a layout file denotes expression syntax. You can also use imported static methods and variables in the expression syntax.

Binding Strings and Integers

  • In this example we bind the first TextView in the layout to name field of the UserModel. Since name field of UserModel is of type String we are able to directly bind it with the TextView.
    <TextView
          android:id="@+id/name_text"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:textColor="@android:color/black"
          android:text="@{user.name}"
          android:textSize="18sp"
          android:textStyle="bold"
          tools:text="Rohit Kumar" />
  •  But age field of UserModel is of type int and when we bind any Integer type to TextView it automatically assumes that the integer is string resource id.
  • Therefore we need to find a way to convert Integer to String. Simplest way to do it is using the String.valueOf() static method. We don’t event need to import String as java.lang.* variables are available by default using databinding.
    <TextView
        android:id="@+id/age_text"
        android:layout_marginTop="8dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="14sp"
        android:text="@{String.valueOf(user.age)}"
        android:textStyle="bold"
        tools:text="28" />
    
  • Interesting thing to note about the above code snippet is that it seems we are able to access private fields (name and age) of the user variable in expression syntax. This is not the case. Expression syntax is always expected to be simpler and therefore we should avoid writing accessor method like @{user.getName()} . The expression parser automatically tries to find the Java Bean accessor name (getXxx() or isXxx()) for your property x.
  • Expression syntax also checks for null values by default. This means that if a TextView is set to @{user.name} and user is null a NullPointerException is not encountered but instead the TextView is set to default value of the field ‘name’ i.e. null.

Binding Collections

  • You must have already noticed that we have still not set text to the hobbies TextView (hobbies_text). This is because we intend to do so using the hobbies list of UserModel.
  • Databinding allows use of collections like Map, List and Array in the expression syntax. As you would do in any Java class first you need to import Collection type and then add data variable for it. You can then access the collection items in the expression syntax using the [] operation. This operator works for arrays,lists and even Maps.
  • In this example we wont need to import List as we can access it directly using the user.hobbies field. Code snippet below shows we set the text to first element of hobbies list using [] operator.
    <TextView
        android:id="@+id/hobbies_text"
        android:layout_marginTop="8dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@android:color/darker_gray"
        android:textSize="14sp"
        android:text="@{user.hobbies[0]}"
        android:textStyle="bold"
        tools:text="Playing Cricket" />

Operators in Expression syntax

  • Almost all the operators both mathematical and conditional are valid in expression syntax. In this example we use some of them while binding data.
  • In this example to avoid IndexOutOfBoundsException we use conditional operator to check the size of our hobbies list before binding the TextView to the first element in the list.
    <TextView
        android:id="@+id/hobbies_text"
        android:layout_marginTop="8dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@android:color/darker_gray"
        android:textSize="14sp"
        android:text="@{user.hobbies.size() > 0 ? user.hobbies[0]:@string/no_hobbies}"
        android:textStyle="bold"
        tools:text="Playing Cricket" />
  • Operators are used to determine whether to display Button or not based on user’s age. We use both comparison and ternary operator for this. Importing View class allowed us to access static field View.GONE and View.VISIBLE.

    <Button
        android:id="@+id/submit_btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/image_view"
        android:layout_marginTop="16dp"
        android:visibility="@{user.age > 10 ? View.VISIBLE : View.GONE}"
        android:text="SUBMIT" />
  • Concatenation operator can also be easily used in expression syntax. In this example we just append two hobbies by adding a space between them using concatenation operator.
    <TextView
        android:id="@+id/hobbies_text"
        android:layout_marginTop="8dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@android:color/darker_gray"
        android:textSize="14sp"
        android:text="@{user.hobbies.size() > 1 ? user.hobbies[0]+' '+user.hobbies[1]:@string/no_hobbies}"
        android:textStyle="bold"
        tools:text="Playing Cricket" />

Inflating the layout file

  • Once you add all the variables and expression syntax in a layout file rebuild your project. Android Data Binding library now generates a Binding class for the layout file. This class is responsible for binding your layout file with data. Name of this Binding class is also as per the name of your layout file converted to Pascal case and with “Binding” appended at the end. Like in this example our activity_main.xml will have a binding class with the name ActivityMainBinding.java.
  • To instantiate this class and inflate layout the you can take any of the two approaches. If you know the Binding class name you can simply inflate the layout using the static method inflate() as shown below. This method inflates the layout and binds the object to it returning reference to the object.
    ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
    

    If you are not aware of the binding class type you can use DataBindingUtil method for inflating the layout and bind the object. This also returns you the reference to the binding class object

    ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
  • This binding class will generate all the logic behind the expression syntax, variables and import statements you added in the layout file. For example when you set text attribute to @{user.name} it is the binding class which interprets the expression syntax, checks for NullPointerException and then gets name field of UserModel object.
  • For every data variable in layout file a instance variable is created in the Binding this. You can therefore bind the data with using the setter method for that variable. The variable defined in layout is just a variable without any value so its important to set value to the variable for Andoid Data Binding to work. For this example we can bind value to variable using setUser() as shown below. Calling this setter method is what actually binds data to your layout.
    public class MainActivity extends AppCompatActivity {
        
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
            setContentView(binding.getRoot());
            UserModel userModel = UserModel.getValidStub();
            //Sets value to the data variable
            binding.setUser(userModel);
            
        }
    }

Handling Events with Expression Syntax

  • Expression syntax can be used to handle events dispatched from a view. This can be achieved in two ways- Method References and Listener Bindings. Lets suppose in this example we want to handle onClick event of the button. First we will a define a inner class and add it as variable to our layout file.
  • For this example we define a inner class ClickHandler with two methods with different signatures
    public class MainActivity extends AppCompatActivity {
    
        ...
               
        public class ClickHandler {
    
            public void handleClick(View view) {
                Toast.makeText(MainActivity.this, "Button clicked " + view.getId(), Toast.LENGTH_SHORT).show();
            }
    
            public void handleClick() {
                Toast.makeText(MainActivity.this, "Button clicked ", Toast.LENGTH_SHORT).show();
            }
    
        }
    }
    
    
  • Then we add a variable of type ClickHandler to the layout.
    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools">
    
        <data>
            <!-- Enable us to reference View class directly  -->
            <import type="android.view.View" />
            <!-- Defines a variable user of type UserModel -->
            <variable
                name="user"
                type="com.example.databindingex.UserModel" />
            <!-- Defines a variable to handle click events -->
            <variable
                name="clickHandler"
                type="com.example.databindingex.MainActivity.ClickHandler" />
        </data>
    
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:padding="16dp"
            tools:context=".MainActivity">
           
            ...
     
    
    </layout>
  • This clickHandler data variable will be handling various events from the layout so before beginning its also important to set the value to our variable.
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
        UserModel userModel = UserModel.getValidStub();
        //Sets value to the data variable
        binding.setUser(userModel);
        binding.setClickHandler(new ClickHandler());
    
    }
  • With Method References you can directly invoke handler method from your layout. The only condition is the handler method should have the same signature as the listener method. Android Data Binding library internally sets a listener to target view and invokes the handler method when the event is fired. Your method references expression is processed at compile time so if the method doesn’t exists or the signature doesn’t match you will receive a compile time error.
  • In this example we have defined a method handleClick(View) with the same signature as View’s onClick(View). This method will be invoked on button click using expression syntax
    <Button
        android:id="@+id/submit_btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/image_view"
        android:layout_marginTop="16dp"
        android:onClick="@{clickHandler::handleClick}"
        android:visibility="@{user.age > 10 ? View.VISIBLE : View.GONE}"
        android:text="SUBMIT" />
  • Listener Binding allows you to invoke handler methods whose signature doesn’t match the listener method. In ClickHandler class we also declared a method with view parameter. We can invoke this method from expression syntax as shown in the code snippet below.
    <Button
        android:id="@+id/submit_btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/image_view"
        android:layout_marginTop="16dp"
        android:onClick="@{(view)->clickHandler.handleClick()}"
        android:visibility="@{user.age > 10 ? View.VISIBLE : View.GONE}"
        android:text="SUBMIT" />

Run the application

  • With all the layout elements binded to data lets run the application and see how this works out. Below is the screenshot of the application running Android Data binding example. As you can see it works perfectly fine and your code is much cleaner than before.Now try clicking the button and you will notice a Toast message as shown in the screenshot below. This confirms that our implementation for button click is working as expected.

 

Note : You must have already noticed that we have not handled displaying image in ImageView using data binding and therefore the ImageView is empty. This will be covered in our next tutorial. Stay Tuned!

Let’s Wrap up for now

This finishes us with the basics of Android Data binding example. I hope you are now much clear how data binding works and how it helps us in cleaning up boiler plate code. If you have any questions feel free to comment down below. But remember there is still a lot more to understand and do with Android Data binding which will be covered in next tutorials. Till then good bye!

If you liked this tutorial and wanted to read more Android Tutorials please visit this


Spread knowledge

1 Reply to “Android Data binding Example [Simple Tutorial]”

Leave a Reply

Your email address will not be published. Required fields are marked *