Data Binding Library

The Data Binding Library allows you to bind UI components in your layouts to data sources in your app using a declarative format rather than programmatically. In this codelab you'll learn how to set it all up, use layout expressions, work with observable objects and create custom Binding Adapters to reduce boilerplate to a minimum.

What you'll build

In this codelab, you'll convert this app to Data Binding:

The app has a single screen which shows some static data and some observable data, meaning that when the data changes, the UI will be automatically updated.

The data is provided by a ViewModel. Model-View-ViewModel is a presentation layer pattern that works very well with Data Binding. Here's a diagram

If you're not yet familiar with the ViewModel class from the Architecture Components libraries, you can check out the official documentation. In summary, it's a class that provides UI state to the view (Activity, Fragment, etc.). It survives orientation changes and acts as an interface to the rest of the layers in your app.

What you'll need

In this step, you download the code for the entire codelab and then run a simple example app.

$ git clone https://github.com/googlecodelabs/android-databinding

Alternatively you can download the repository as a Zip file:

Download Zip

  1. Unzip the code
  2. Open the project Android Studio version 3.2 or newer.

Run the app

The default activity opens and looks like this:

This screen shows some data and lets the user click on a button to increment a counter and update a progress bar. It uses SimpleViewModel. Open it and take a look.

It exposes

Also, it lets the user increment the number of likes with onLike().

While SimpleViewModel doesn't have the most interesting functionality, it's still ok. On the other hand, PlainOldActivity has a number of problems:

With the Data Binding library we're going to fix all these problems, moving logic out of the activity into places where it's reusable and easier to test.

This project already has Data Binding enabled, but when you want to use it in your own projects the first step is to enable the library in the modules that will use it.

build.gradle

android {
...
    dataBinding {
       enabled true
    }
}

Now you will convert the layout to a Data Binding layout.

Open plain_activity.xml. It is a regular layout with a Constraint Layout as the root element.

In order to convert the layout to Data Binding, you need to wrap the root element in a <layout> tag. You'll also have to move the namespace definitions (the attributes that start with xmlns:) to the new root element.

Handily, Android Studio offers a way to do this automatically.

Your layout should now look like this:

<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>

   </data>
   <android.support.constraint.ConstraintLayout
           android:layout_width="match_parent"
           android:layout_height="match_parent">

       <TextView
...

Check out the <data> tag. That's where we put the layout variables.

Layout variables are used to write layout expressions. Layout expressions are placed in the value of element attributes and they use the @{expression} format. Here are some examples:

// Some examples of complex layout expressions
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
// Bind the name property of the viewmodel to the text attribute
android:text="@{viewmodel.name}"
// Bind the nameVisible property of the viewmodel to the visibility attribute
android:visibility="@{viewmodel.nameVisible}"
// Call the onLike() method on the viewmodel when the View is clicked.
android:onClick="@{() -> viewmodel.onLike()}"

Check out a complete description of the language here.

Let's bind some data!

Let's start with some static data binding for now.

    <data>
        <variable name="name" type="String"/>
        <variable name="lastName" type="String"/>
    </data>
        <TextView
                android:id="@+id/plain_name"
                android:text="@{name}" 
        ... />

Layouts expressions start with an @ symbol and are wrapped inside curly braces { }.

Because name is a String, Data Binding is going to know how to set that value in the TextView. You'll learn how to deal with different layout expression types and attributes later on.

        <TextView
                android:id="@+id/plain_lastname"
                android:text="@{lastName}"
        ... />

You can find the result of these operations in plain_activity_solution_2.xml.

Now we need to modify the Activity so that it inflates a Data Binding layout correctly:

The layout is ready but we need to do some changes in the activity. Open PlainOldActivity.

Because we're using a Data Binding layout, the inflation is done in a different way.

In onCreate, replace:

setContentView(R.layout.plain_activity)

with:

val binding : PlainActivityBinding =
    DataBindingUtil.setContentView(this, R.layout.plain_activity)

Why are we creating a variable? Because we need a way to set those layout variables we declared in our <data> block. That's what the binding object is for. Binding classes are generated automatically by the library. If you want, open PlainActivitySolutionBinding, and take a look around. It's plain Java code.

    binding.name = "Your name"
    binding.lastName = "Your last name"

And that's it. You just bound data using the library.

We can start removing old code:

You can find the result of these operations in PlainOldActivitySolution2.

You can now run the app. You'll see that your name has replaced Ada's.

So far we've explored how to show data to the user but with the Data Binding library you can also handle user events and invoke actions on layout variables.

We're going to clean up our layout a bit before we do that.

    <data>
        <variable
                name="viewmodel"
                type="com.example.android.databinding.basicsample.data.SimpleViewModel"/>
    </data>

Instead of accessing the variables directly, we'll call the viewmodel properties.

        <TextView
                android:id="@+id/plain_name"
                android:text="@{viewmodel.name}"
... />
        <TextView
                android:id="@+id/plain_lastname"
                android:text="@{viewmodel.lastName}"
... />

Also, we're going to react to clicks on the "Like" button.

android:onClick="onLike"

with

android:onClick="@{() -> viewmodel.onLike()}"

The former onClick attribute used an unsafe mechanism in which the onLike() method in the activity or fragment is called when the view is clicked. If that method with the exact right signature doesn't exist, the app crashes.

The new way is much safer because it's checked at compile time and uses a lambda expression to call the onLike() method of the view model.

You can find the result of these operations in plain_activity_solution_3.xml.

Now let's remove things we don't need from the activity:

1. Replace

    binding.name = "Your name"
    binding.lastName = "Your last name"

with

    binding.viewmodel = viewModel

2. Remove the onLike method in the activity, as it's bypassed now.

You can find the result of these operations in PlainOldActivitySolution3.

If you run the app you'll see that the button doesn't do anything. That's because we're not calling updateLikes() anymore. Let's implement that properly.

We created a static binding in the previous step. If you open the view model you'll find that name and lastName are just Strings, which is fine because they are not going to change. However, likes is modified by the user.

var likes =  0

Instead of explicitly updating the UI when this value changes, we're going to make it observable.

There are multiple ways to implement observability. You can use observable classes, observable fields, or, the preferred way, LiveData. The full documentation on that is here.

We're going to use ObservableFields as they are simpler.

Replace

    var likes = 0
       an strict strong typing in this codelab, so I'd advise against using "very" private set // This is to prevent external modification of the variable.

with the new observable field:

    val likes = ObservableInt()
    val popularity = ObservableField<Popularity>(NORMAL)

Also, replace

    fun onLike() {
        likes++
    }

    /**
     * Returns popularity in buckets: [Popularity.NORMAL],
     * [Popularity.POPULAR] or [Popularity.STAR]
     */
    val popularity: Popularity
        get() {
            return when {
                likes > 9 -> Popularity.STAR
                likes > 4 -> Popularity.POPULAR
                else -> Popularity.NORMAL
            }
        }

with

    fun onLike() {
        likes.set(likes.get() + 1)

        popularity.set(likes.get().let {
            when {
                it > 9 -> Popularity.STAR
                it > 4 -> Popularity.POPULAR
                else -> Popularity.NORMAL
            }
        })
    }

As you can see, an Observable field's value has to be set with set() and read with get(). This mechanism allows the library to update the UI when the value changes.

If you rebuild the project you'll find that the activity is not compiling. We're accessing likes directly from the activity, which we don't need anymore:

    private fun updateLikes() {
        findViewById<TextView>(R.id.likes).text = viewModel.likes.toString()
        findViewById<ProgressBar>(R.id.progressBar).progress =
            (viewModel.likes * 100 / 5).coerceAtMost(100)
...

Open PlainOldActivity and remove all the private methods in the activity and their calls. The activity is now as simple as it gets.

class PlainOldActivity : AppCompatActivity() {

    // Obtain ViewModel from ViewModelProviders
    private val viewModel by lazy { ViewModelProviders.of(this).get(SimpleViewModel::class.java) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val binding : PlainActivityBinding =
            DataBindingUtil.setContentView(this, R.layout.plain_activity)

        binding.viewmodel = viewModel
    }
}

You can find the result of these operations in SolutionActivity.

In general, moving code out of the activity is great for maintainability and testability.

Let's bind the TextView showing the number of likes to the observable integer. In plain_activity.xml:

        <TextView
                android:id="@+id/likes"
                android:text="@{Integer.toString(viewmodel.likes)}"
...

If you run the app now, the number of likes is incremented as expected.

Let's recap what we've done so far:

  1. Name and last name are exposed as strings from the view model.
  2. The button's onClick attribute is bound to the view model via a lambda.
  3. The number of likes is exposed from the view model via an observable integer and bound to a text view so it's refreshed automatically when it changes.

So far we've used attributes like android:onClick and android:text. Let's explore other properties and create our own:

When you bind a string (or an observable string) to an android:text attribute it's pretty obvious what's going to happen but how is it happening?

With the Data Binding library, almost all UI calls are done in static methods called Binding Adapters.

The library provides a huge amount of Binding Adapters. Check them out here. Here's an example for the android:text attribute:

    @BindingAdapter("android:text")
    public static void setText(TextView view, CharSequence text) {
        // Some checks removed for clarity

        view.setText(text);
    }

Or the android:background one:

    @BindingAdapter("android:background")
    public static void setBackground(View view, Drawable drawable) {
        if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
            view.setBackground(drawable);
        } else {
            view.setBackgroundDrawable(drawable);
        }
    }

There's no magic in Data Binding. Everything is resolved at compilation time and it's accessible for you to read in the generated code.

Let's work on the progress bar. We want it to:

We're going to create custom Binding Adapters for this.

Open the BindingAdapters.kt file in the utils package. It doesn't matter where you create them, the library will find them. In Kotlin, static methods can be created by adding functions to the top level of a Kotlin file.

Look for the Binding Adapter for the first requirement, hideIfZero:

    @BindingAdapter("app:hideIfZero")
    @JvmStatic fun hideIfZero(view: View, number: Int) {
        view.visibility = if (number == 0) View.GONE else View.VISIBLE
    }

This binding adapter:

In the plain_activity layout, look for the progress bar and add the hideIfZero attribute:

    <ProgressBar
            android:id="@+id/progressBar"
            app:hideIfZero="@{viewmodel.likes}"
...

Run the app and you'll see that the progress bar shows up when you click for the first time on the button. However, we still need to change its value and color:

You can find the result of these steps in plain_activity_solution_4.xml.

For the progress value, we're going to use a Binding Adapter that takes the maximum value and the number of likes. Open the BindingAdapters file and look for this one:

    /**
     *  Sets the value of the progress bar so that 5 likes will fill it up.
     *
     *  Showcases Binding Adapters with multiple attributes. 
     *  Note that this adapter is called whenever any of 
     *  the attributes change.
     */
    @BindingAdapter("app:progressScaled", "android:max", requireAll = true)
    @JvmStatic fun setProgress(progressBar: ProgressBar, likes: Int, max: Int) {
        progressBar.progress = (likes * max / 5).coerceAtMost(max)
    }

This Binding Adapter is not used if any of the attributes are missing. This happens at compile time. The method takes 3 parameters now (the view it applies to + the number of attributes defined in the annotation).

The requireAll parameter defines when the binding adapter is used:

Next, add the attributes to the XML:

        <ProgressBar
                android:id="@+id/progressBar"
                app:hideIfZero="@{viewmodel.likes}"
                app:progressScaled="@{viewmodel.likes}"
                android:max="@{100}"
...

We're binding the progressScaled attribute to the number of likes and we're just passing a literal integer to the max attribute. If you don't add the @{} format, Data Binding won't be able to find the correct Binding Adapter.

You can find the result of these steps in plain_activity_solution_5.xml.

If you run the app, you'll see how the progress bar fills up as expected.

Practice makes perfect. Create:

You'll find solutions in the BindingAdapters.kt file, and in SolutionActivity and the solution.xml layout.

Congratulations! You completed the codelab so you should know how to create Data Binding layouts, add variables and expressions to it, use observable data, and make your XML layouts more meaningful with custom attributes via custom Binding Adapters.

Now check out the samples for more advanced usages and the documentation for the complete picture.