Scratching the surface of Android's new Data Binding Library and Retrofit - Part 2

Scratching the surface of Android's new Data Binding Library and Retrofit - Part 1

Hi everyone, welcome to the part 2 of this tutorial series. If you didn't read the Part-1 please read that before trying this tutorial.

Full source code of this project will be found here.

To recap let's see what we've done so far and what is our goal today. We're building a simple app which will show a list of upcoming movies. We're using Retrofit by Square and Android Data Binding library to do our job.

In the last tutorial we've fetched our movie data from a rest api using Retrofit. Today we're gonna bind our data to the view using Android Data Binding library.

Android Studio can show some red marks in your layout files and codes, but fear not, that's because google is still integrating data binding to it.

Our final app will look like this,

Final output

Let's build our main view and bind the data with awesomeness.

Before going further let's see what data binding looks like. Suppose you have a User model, which holds first name, last name and age of the user. Now if I tell you to show them in a view consist of three TextView you'll create a XML layout file like this,

...
<LinearLayout  
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tvFirstName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <TextView
        android:id="@+id/tvLastName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <TextView
        android:id="@+id/tvAge"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</LinearLayout>  

User model class will look like kind of this,

public class User {  
    private String firstName;
    private String lastName;
    private int age;

    public User(String firstName, String lastName, int age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }

    public String getFirstName() {return firstName;}
    public String getLastName() {return lastName;}
    public String getAge() {return age;}
}

And to set the data you'll write code like this,

...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        User user = new User("Shahriar", "Anwar", 26); // Mock object

        TextView tvFirstName = (TextView) findViewById(R.id.tvFirstName);
        TextView tvLastName = (TextView) findViewById(R.id.tvLastName);
        TextView tvAge = (TextView) findViewById(R.id.tvAge);

        tvFirstName.setText(user.getFirstName());
        tvFirstName.setText(user.getLastName());
        tvFirstName.setText(String.valueOf(user.getAge()));
    }
...

All of the above code looks ugly and you're still doing this everyday with a full mug of coffee. BORING!!!

Now let's make it interesting with data binding. After applying data binding, your above view will look like this, (Layout will exactly look like the following code)

<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <import type="me.anwarshahriar.example.models.User"/>
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tvFirstName"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="{ @user.firstName }"/>
        <TextView
            android:id="@+id/tvLastName"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="{ @user.lastName }"/>
        <TextView
            android:id="@+id/tvAge"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="{ String.valueOf(@user.age) }"/>
    </LinearLayout>
</layout>

And your glue code for pushing data will be like this,

...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //Magical binding object
        ActivityMainBinding binding = 
            DataBindingUtil.setContentView(this, R.layout.activity_main);

        User user = new User("Shahriar", "Anwar", 26);

        // Magical binding happens and data shows in the view
        binding.setUser(aMovie); 
    }
...

If we run the application then the app will show the user's info as before but with less glue code.

Now, let's see what's happening here. We've imported our model type for the view in the xml in the data section using fully qualified name for the model (This will vary as your package name).

    <data>
        <import type="me.anwarshahriar.example.models.User"/>
    </data>

Then we've used the property of the user directly in the TextViews,

<TextView  
    ...
    android:text="{@user.firstName}"/>

When android will reach that line it'll evaluate the expression { @user.firstName } and if we provide a model object of the user class then it'll call the appropriate getter method user.getFirstName() and it'll set the text of the TextView with that value.

So, if you look at the tvAge TextView you can see that we can use java statements in the binding too,

<TextView  
    ...
    android:text="{String.valueOf(@user.age)}"/>

Here's we didn't import java.lang.String beacause it belongs to java.lang package. But if you wan't to use other classes of different package then you have to import that class too.

Let's see what happenning in our onCreate method and how we provide data to the view. Here all the magic takes place. We just create a binding with this,

ActivityMainBinding binding =  
            DataBindingUtil.setContentView(this, R.layout.activity_main);

Here ActivityMainBinding will be auto generated by the data binding plugin follows by your activity name. We create a binding object which can take data from us and bind the data to the view attached with it. So we create our mock object,

User user = new User("Shahriar", "Anwar", 26);  

And give the data to the binding object to bind with the view,

binding.setUser(aMovie);  

That's it. You can give any user model to bind and it'll bind the data to view. Now we have decoupled view and activity. We don't have to worry about referencing those view. Any change will view won't affect your activity anymore and you have less code to maintain. Let's enjoy your coffee when it's still hot.

I think now you've got a working understanding of how data binding works.

Let's dig into our application and make it cool.

We'll use RecyclerView to show our movie list. So open up the project you've made in the first part of the tutorial, add dependency for the 'RecyclerView',

dependencies {  
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.2.0'
    compile 'com.squareup.retrofit:retrofit:1.9.0'
    compile 'com.android.support:recyclerview-v7:22.2.+'
}

and put the following code into your main layout file associate with your application's main activity, (In my project it's called activity_test.xml)

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rvMovie"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</RelativeLayout>  

This is simple, there is still no data binding magic (We'll get back here later :P).
Now create layout file called movie_item.xml and put the following code,

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

    <data>

        <variable
            name="movie"
            type="me.anwarshahriar.moviepedia.models.Movie" />
    </data>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="16dp">

        <ImageView
            android:id="@+id/ivPoster"
            android:layout_width="72dp"
            android:layout_height="92dp"
            android:scaleType="centerCrop"
            app:imageUrl="@{movie.poster}" /> <!-- We'll talk about it later -->

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_marginLeft="16dp"
            android:layout_toRightOf="@+id/ivPoster"
            android:orientation="vertical">

            <TextView
                android:id="@+id/tvTitle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{movie.title}"
                android:textColor="#353d35"
                android:textSize="20sp" />

            <TextView
                android:id="@+id/tvSubtitle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="8dp"
                android:text="@{movie.year}"
                android:textColor="#747474"
                android:textSize="18sp"
                android:textStyle="italic" />
        </LinearLayout>

    </RelativeLayout>
</layout>  

Here we've just put the code that we need to work with data binding. We've set movie title and the release date of the movie and the poster of the movie. Now if you look closely you'll find that there's a wired thing going on in the ImageView of the poster.

<ImageView  
    ...
    app:imageUrl="@{movie.poster}" />

Actually we'll load image from network using Picasso library which needs the view and an url to load the image in that associated view. So, we're passing the url along with the view to a method which will handle this task for us. So what we can understand is, if we want we can handle the binding from the code too.

Now let's write the code to handle the image loading to the ImageView. First create a package named utils and create a class named BindingUtils in that package. Add Picasso to the dependency list of your module's build.gradle file,

dependencies {  
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.2.0'
    compile 'com.squareup.retrofit:retrofit:1.9.0'
    compile 'com.android.support:recyclerview-v7:22.2.+'
    compile 'com.squareup.picasso:picasso:2.5.2'
}

Add the following code in the BindingUtils class,

public class BindingUtils {  
    @BindingAdapter({"bind:imageUrl"})
    public static void loadImage(ImageView view, String url) {                
          Picasso.with(view.getContext())
              .load(url).error(R.mipmap.ic_launcher)
              .into(view);
    }
}

So, the url of the movie poster will be passed by the declaration app:imageUrl="@{movie.poster}" to the loadImage method along with the ImageView instance, and Picasso will lazy load the image from the given url to the provided ImageView. We've used @BindingAdapter({"bind:imageUrl"}) to catch the url from the declaration. So BindingAdapter will help us to map our declaration from the xml to the appropriate method and all the magic behind this will be handled by android data binding library.

Let's create the adapter to work with our RecyclerView.

Create a package named adapters and create a class named MovieAdapter and put the following code there,

public class MovieAdapter extends RecyclerView.Adapter<MovieAdapter.BindingHolder> {  
    private List<Movie> movies;

    public static class BindingHolder extends RecyclerView.ViewHolder {
        private ViewDataBinding binding;

        public BindingHolder(View v) {
            super(v);
            binding = DataBindingUtil.bind(v);
        }

        public ViewDataBinding getBinding() {
            return binding;
        }
    }

    public MovieAdapter(List<Movie> movies) {
        this.movies = movies;
    }

    @Override
    public BindingHolder onCreateViewHolder(ViewGroup parent, int type) {
        View v = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.movie_item, parent, false);
        BindingHolder holder = new BindingHolder(v);
        return holder;
    }

    @Override
    public void onBindViewHolder(BindingHolder holder, int position) {
        final Movie aMovie = movies.get(position);
        holder.getBinding().setVariable(BR.movie, aMovie);
        holder.getBinding().executePendingBindings();
    }

    @Override
    public int getItemCount() {
        return movies.size();
    }
}

If you feel clumsy about RecyclerView and RecyclerView.Adapter please go to this link and give it a thorough reading then come back here to continue.

Here we're using data binding that's why we don't need to have any reference to the views used by the adapter instead we'll hold a binding object (remember from the top) for each view and we'll return it when asked by the adapter. So, our ViewHolder looks like this,

    public static class BindingHolder extends RecyclerView.ViewHolder {
        private ViewDataBinding binding;

        public BindingHolder(View v) {
            super(v);
            binding = DataBindingUtil.bind(v);
        }

        public ViewDataBinding getBinding() {
            return binding;
        }
    }

In the above code we've created a binding object in the constructor for the view of type ViewDataBinding and add a getter method getBinding() for later retrieval.

We've created our ViewHolder with the following code,

    @Override
    public BindingHolder onCreateViewHolder(ViewGroup parent, int type) {
        View v = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.movie_item, parent, false);
        BindingHolder holder = new BindingHolder(v);
        return holder;
    }

Then in onCreateViewHolder we've inflated appropriate view for each item, in this case using our previously created movie_item.xml layout file. Then we've created a BindingHolder object which contains the binding object for this specific view and returned the holder.

Now it's time to bind the data. So we've done that here,

    @Override
    public void onBindViewHolder(BindingHolder holder, int position) {
        final Movie aMovie = movies.get(position);
        holder.getBinding().setVariable(BR.movie, aMovie);
        holder.getBinding().executePendingBindings();
    }

We've got our movie object from the movie list with the position provided by the adapter and then we've used the binding object of the provided holder and set the movie object with the appropriate id.
In the code holder.getBinding().setVariable(BR.movie, aMovie) you are seeing a resource file BR similar to R is auto generated which provides id for appropriate model to map. Then to see the immediate reflection of the data change in the screen we've called executePendingBinding which forcefully bind the data to view to reflect the change immediately. That's all for the adapter. Sweet! isn't it?

Let's go to the main activity of our project. Here in my project it's called TestActivity. Put the following code,

    ...
    RecyclerView rvMovie;
    List<Movie> movies;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        init();
    }

    private void init() {
        movies = new ArrayList<Movie>();

        Picasso.with(getApplicationContext()).setIndicatorsEnabled(true);

        rvMovie = (RecyclerView) findViewById(R.id.rvMovie);
        rvMovie.setLayoutManager(new LinearLayoutManager(this));

        fetchComingSoonMovies();
    }

    private void fetchComingSoonMovies() {
        RestAdapter restAdapter = new         
        RestAdapter.Builder()
            .setEndpoint("http://www.myapifilms.com/imdb").build();
        MovieService movieService = restAdapter.create(MovieService.class);
        movieService.getComingSoonMovies(new Callback<List<MoviesByDate>>() {
            @Override
            public void success(List<MoviesByDate> moviesByDates, Response response) {
                // Add this part to initialize the adapter
                for (MoviesByDate item : moviesByDates) {
                    movies.addAll(item.getMovies());
                }
                MovieAdapter adapter = new MovieAdapter(movies);
                rvMovie.setAdapter(adapter);
            }

            @Override
            public void failure(RetrofitError error) {
                Toast.makeText(getApplicationContext(), 
                    "Unexpected error occured", Toast.LENGTH_LONG)
                    .show();
            }
        });
    }

Here we've just initialized the MovieAdapter and set it to our RecyclerView. Now if you run the app you'll see the expected list of movies.

Let's make it more interesting

Now you can see after starting the app nothing is there for few seconds and then the list shows. That's because of loading time of movies from the web. We can show a loading sign to the user so that they understand that, the application is working.

We can do that by adding a simple ProgressBar and make it invisible when data is loaded. But we're determined to use data binding so let's do that.

At first change the following line of code above the onCreate method,

    // List<Movie> movies; // Old declaration
    ObservableArrayList<Movie> movies; // New Declaration

and chage the following code of the top of init method like this,

private void init() {  
    // movies = new ArrayList<Movie>(); // Old declaration
    movies = new ObservableArrayList<Movie>(); // New declaration
    ...
}

then change onCreate to this,

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ActivityTestBinding binding = 
            DataBindingUtil.setContentView(this, R.layout.activity_test);

        init();

        binding.setMovies(movies);
    }

Let's change the main layout file too and make it look like this,

<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <import type="android.databinding.ObservableArrayList" />
        <import type="android.view.View"/>
        <import type="me.anwarshahriar.moviepedia.models.Movie"/>

        <variable name="movies" type="ObservableArrayList&lt;Movie>" />
    </data>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ProgressBar
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:visibility='@{movies.size() > 0 ? View.GONE : View.VISIBLE}'/>

        <android.support.v7.widget.RecyclerView
            android:id="@+id/rvMovie"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>

    </RelativeLayout>
</layout>  

Don't be scared, what we've done here is pretty simple, trust me. We've used a new type ObservableArrayList part of data binding library. Just think like that we have a list and we want to know if anything changes in that list. So, we want to observe the list, hence ObservableArrayList. So we want our movie list to observe and if the list is empty we want to show a ProgressBar and hide the progress bar when the list is not empty anymore.

So, we've changed our List type to ObservableArrayList to watch over. To use that list in our view we need to import the class in the data section and we need a variable to bind to the data source provided by the activity. We did it here,

<data>  
    <import type="android.databinding.ObservableArrayList" />
    ...
    <variable name="movies" type="ObservableArrayList&lt;Movie>" />
</data>  

Then to make the ProgressBar visible and invisible we need to use two constants which are belong to android.view.View class. That's why we imported it here too,

<data>

    <import type="android.databinding.ObservableArrayList" />
    <import type="android.view.View"/>
    ...
    <variable name="movies" type="ObservableArrayList&lt;Movie>" />
</data>  

Then we've change the visibility of the progressbar according to the size of the movies ObservableArralyList,

<ProgressBar  
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerHorizontal="true"
    android:visibility='@{movies.size() > 0 ? View.GONE : View.VISIBLE}'/>

And at last we've bound the movies list to the view from the code,

ActivityTestBinding binding =  
    DataBindingUtil.setContentView(this, R.layout.activity_test);
...
binding.setMovies(movies);  

That's all. Just run the app, it'll show a progress bar while movies are loading from the web and when the list arrives the progress bar disappears. Final outcome will look like this,

Final output

Now incase you didn't notice that whole app contains less than 220 lines of actual code with views and everything to do all the work including fetching data from web, parsing, binding data to view, showing the movies in the list and prompting user about the app status. At last but not least all of your code is loosely coupled and you have too little to worry about maintenance.

The full project source code is given at the top of the tutorial.

Thank you for bearing with me all this long way. Data binding may be challenging if you're too comfortable to work with previous android workflow but give it a try, you'll be rewarded. Stay tuned with my blog and,

Happy coding!

comments powered by Disqus