Adapter / Data Binding / RecyclerView / ViewHolder

Data Binding – Part 1

At Google I/O 2015 a number of new Android libraries and tools were announced. One of them was the all new Data Binding library and in this series we’ll take a look at the library and explore some of the powerful features it provides.

Before we begin it is worth pointing out that, at the time of writing, the Data Binding library is still in beta so some of the API calls may change before the full release.

The Data Binding library provides a mechanism for linking the data which will be displayed within our layouts to some kind of back-und data source. For this example code we’re going to put together a very simple Twitter client and use the Data Binding library to the data coming from the Twitter API. I’m not going to provide a tutorial on the Twitter API, or the basic app design – it retrieves the last 50 items from your home timeline using the Twitter4J library and displays them within a RecyclerView. All of the code is published – so feel free to browse it to understand this side of the app. The part that is of interest for this series of articles is the data and how it gets bound to Views.

Let’s start by looking at the data model:

public class Status {

    private final String name;
    private final String screenName;
    private final String text;
    private final String imageUrl;

    public Status(@NonNull String name, @NonNull String screenName, @NonNull String text, @NonNull String imageUrl) {
        this.name = name;
        this.screenName = screenName;
        this.text = text;
        this.imageUrl = imageUrl;
    }

    public String getName() {
        return name;
    }

    public String getScreenName() {
        return screenName;
    }

    public String getText() {
        return text;
    }

    public String getImageUrl() {
        return imageUrl;
    }
}

This simply holds the values that we want to display to the user and each item in the RecyclerView will bind to a single Status object. This gets created from a twitter4j.Status object – which is a single tweet as retrieved by Twitter4J. So we also have a class which will convert a twitter4j.Status to our Status:

public class StatusMarshaller {

    public List<Status> marshall(List<twitter4j.Status> statuses) {
        List<Status> newStatuses = new ArrayList<>(statuses.size());
        for (twitter4j.Status status : statuses) {
            newStatuses.add(marshall(status));
        }
        return newStatuses;
    }

    private Status marshall(twitter4j.Status status) {
        User user = status.getUser();
        return new Status(user.getName(), user.getScreenName(), status.getText(), user.getBiggerProfileImageURL());
    }
}

There’s really nothing very clever going on here – it’s pure Java stuff and nothing specific to Data Binding, or even Android, for that matter!

It’s worth pointing out that strictly speaking this is not required and we could actually bind the UI directly to the twitter4j.Status object – and that would certainly be more efficient for the early examples. However, the Data Binding library utilises an MVVM (Model-View-ViewModel) pattern – the Model is twitter4j.Status; the View is our layout (which we’ll come to shortly); and the ViewModel is our Status object. The ViewModel is a representation of the data which is specifically designed with the View in mind – to make it easy to consume. While the Model and ViewModel are almost identical (as they are currently) it is difficult to see the benefit of this, but as we dig deeper the benefits of this should become apparent.

The next thing to look at is the Adapter for our RecyclerView:

public class StatusAdapter extends RecyclerView.Adapter<StatusViewHolder> {
    private final List<Status> statuses;
    private final StatusMarshaller marshaller;

    public static StatusAdapter newInstance() {
        List<Status> statuses = new ArrayList<>();
        StatusMarshaller marshaller = new StatusMarshaller();
        return new StatusAdapter(statuses, marshaller);
    }

    StatusAdapter(List<Status> statuses, StatusMarshaller marshaller) {
        this.statuses = statuses;
        this.marshaller = marshaller;
    }

    @Override
    public StatusViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        Context context = parent.getContext();
        LayoutInflater inflater = LayoutInflater.from(context);
        View statusContainer = inflater.inflate(R.layout.status_item, parent, false);
        return new StatusViewHolder(statusContainer);
    }

    @Override
    public void onBindViewHolder(StatusViewHolder holder, int position) {
        Status status = statuses.get(position);
        holder.bind(status);
    }

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

    public void setStatuses(List<twitter4j.Status> statuses) {
        this.statuses.clear();
        this.statuses.addAll(marshaller.marshall(statuses));
        notifyDataSetChanged();
    }

}

Now we’re into Android territory, but again there’s really nothing unusual going on – it is a standard RecyclerView.Adapter implementation with nothing specific to Data Binding – that gets done in the StatusViewHolder as we’ll see shortly – but as far as the Adapter is concerned this is just a standard RecyclerView.ViewHolder. The only thing that is really happening here that’s specific to the MVVM pattern is the marshalling from twitter4j.Status to .data.Status in setStatuses(). So let’s now turn our attention to the item layout:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

  <data>

    <variable
      name="status"
      type="com.stylingandroid.databinding.data.Status" />

  </data>

  <RelativeLayout
    android:id="@+id/status_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
      android:id="@+id/status_avatar"
      android:layout_width="64dp"
      android:layout_height="64dp"
      android:layout_alignParentLeft="true"
      android:layout_alignParentStart="true"
      android:layout_alignParentTop="true"
      android:layout_margin="8dip"
      android:contentDescription="@null" />

    <TextView
      android:id="@+id/status_name"
      style="@style/Status.Name"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_alignParentTop="true"
      android:layout_marginTop="8dip"
      android:layout_toEndOf="@id/status_avatar"
      android:layout_toRightOf="@id/status_avatar"
      android:text="@{status.name}" />

    <TextView
      android:id="@+id/status_screen_name"
      style="@style/Status.ScreenName"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_alignBaseline="@id/status_name"
      android:layout_marginLeft="4dip"
      android:layout_marginStart="4dip"
      android:layout_toEndOf="@id/status_name"
      android:layout_toRightOf="@id/status_name"
      android:text="@{&quot;@&quot; + status.screenName}" />

    <TextView
      android:id="@+id/status_text"
      style="@style/Status.Text"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_alignLeft="@id/status_name"
      android:layout_alignParentEnd="true"
      android:layout_alignParentRight="true"
      android:layout_alignStart="@id/status_name"
      android:layout_below="@id/status_name"
      android:maxLines="2"
      android:singleLine="false"
      android:text="@{status.text}" />

  </RelativeLayout>
</layout>

This is where we get to the heart of how the Data Binding library works. The RelativeLayout with the ID status_container is a pretty standard layout (but some of the Views have some weird stuff in some of their attributes, which we’ll come to in a moment). But this parent layout is wrapped in its own <layout> parent which is specific to the Data Binding library. As well as the RelativeLayout, the <layout> also contains a data section which defines the data objects that we’re going to bind to – in this case we’re declaring a .data.Status named status. We can now reference this throughout the layout.

So within the layout we have the weird android:text attributes – these are essentially getters from our .data.Status object. The @{} wrapper indicates that this is a data binding expression, and the status.name is the equivalent of calling status.getName() in Java. That’s the heart of how Data Binding works but there’s an awful lot more to it that that as we shall see.

But there is something a bit more complex going on in the TextView with ID status_screen_name: @{&quot;@&quot; + status.screenName}. Although this looks quote scary, all it is is some simple string concatenation to prepend a ‘@’ symbol to the Twitter screen name. We need to put the @ inside quotes which need to be escaped within an XML layout, but essentially all it is doing is: "@" + status.getScreenName(); in Java. However this does further hint that there is rather more that we can do with this expression language, as we’ll find out as we progress.

Now that we have this defined we need to hook it up, and this is done through the StatusViewHolder:

public class StatusViewHolder extends RecyclerView.ViewHolder {
    private StatusItemBinding binding;

    public StatusViewHolder(View itemView) {
        super(itemView);
        binding = DataBindingUtil.bind(itemView);
    }

    public void bind(Status status) {
        binding.setStatus(status);
    }

}

That’s a lot simpler than a normal ViewHolder where we need to find the child Views within the parent layout in the constructor and then set their attributes in the bind() method. The first thing to notice is that we’re using StatusItemBinding which we’re obtaining from DataBindingUtil.bind(), but we haven’t covered that. The reason is that we don’t need to write it because it is generated for us by the Data Binding library.

We’ll cover what this in a bit more depth in the next article, but we actually have a basic, working app at this point:

Part1

The source code for this article is available here.

© 2015, Mark Allison. All rights reserved.

Copyright © 2015 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.

5 Comments

  1. Thanks for this explanatory data binding article. Very helpful. Data binding appears to be a really useful feature.

    One minor question: the article says “and the ModelView is our Status object.” But, I’m wondering if that should read “and the ViewModel is our Status object. ”

    Again, thank you for this article. Cheers.

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.