Data Binding / ImageView

Data Binding – Part 3

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.

In the previous article we covered some basic Data Binding of text items to TextViews within our layout and there were some references which hinted that we’ll also be adding images. This is a little more complex than simple text because we get a URL from the Twitter feed and this does not equate directly to the Bitmap object we need to bind an ImageView to. In part this should begin to suggest that the difference between the Model and ViewModel in the MVVM pattern which the Data Binding library adopts. In this case the Model contains a URL but the View requires a Bitmap, and the conversion from the Model to the ViewModel should provide what the View requires. While that is strictly true, and that is the purpose that the ViewModel is designed for, in this case there is an alternative approach that we can use. The reasoning behind this is that conversion from a URL to a Bitmap is a non-trivial task requiring a network transaction which cannot be efficiently performed as part of a simple object conversion pass. Also, the logic for doing this is already encapsulated in to third-party libraries which are mature and well tested, but may not be simple to incorporate in to the conversion itself.

The other issue that we have here is that because the network translation required to obtain each Bitmap is expensive, to do it at the point where we perform the Model->ViewModel conversion would mean that we do it for all items in the List that is passes to the Adapter regardless of whether they are actually displayed or not. This would be hugely inefficient, and would be a massive waste of the users’ data, so we would be much better of deferring this until we know that we require a Bitmap for display. So it is much more efficient to build this in to the binding pass which only occurs when we are actually displaying an item within the RecyclerView.

I’ve already mentioned third-party image loading libraries, and we previously mentioned a compile dependency on Glide. For those unfamiliar with Glide it provides an image retrieval / conversion / loading pipeline which is invoked by simply chaining up operations:

Glide.with(context).load(url).into(imageView);

It automatically performs an asynchronous retrieval of the image and then sets it on the ImageView once complete. It can incorporate caching and all kinds of other good stuff, but we’ll just use it in its basic form here.

So the obvious thought would be that we can simply incorporate this in to our Data Binding expression for our ImageView:

<?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="status"
      type="com.stylingandroid.databinding.data.Status" />

    <variable
      name="glide"
      type="com.bumptech.glide.Glide" />

  </data>

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

    <!--
      IMPORTANT NOTE: This will *not* work!
    -->

    <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"
      app:imageUrl="@{glide.load(status.imageUrl).into(this)}" />
    .
    .
    .
  </RelativeLayout>
</layout>

There is actually one big problem with this. A quick check of the Data Binding expression language states:

A few operations are missing from the expression syntax that you can use in Java.

this
super
new
Explicit generic invocation

We can’t use this within an expression to refer to the View being bound to. However, all is not lost as there is a mechanism build in to the Data Binding library that we can use to overcome this – custom setters. There may be instances, particularly when you are using custom Views, when you may wish to invoke a specific setter on a particular View, and custom setters provide this functionality. It is also possible to use a custom setter to perform an implicit conversion and that’s precisely what we need here because we wish to use Glide to perform the required conversion from a URL to a Bitmap.

So let’s define our custom setter:

public final class DataBinder {

    private DataBinder() {
        //NO-OP
    }

    @BindingAdapter("imageUrl")
    public static void setImageUrl(ImageView imageView, String url) {
        Context context = imageView.getContext();
        Glide.with(context).load(url).into(imageView);
    }
}

This is a simple utility class which contains a single method named setImageView() which takes an ImageView and a String as arguments. When invoked it performs the Glide operation that we mentioned previously. The one important thing here is the @BindingAdapter("imageUrl") annotation which is telling the Data Binding library this this is a custom setter named “imageUrl”. We don’t need to declare this in any config files or anything – the annotation itself is enough to ensure that this is recognised at build time, and we can now invoke this from an expression:

<?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="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"
      app:imageUrl="@{status.imageUrl}"/>
    .
    .
    .
  </RelativeLayout>
</layout>

We need to reference the custom setter via the app namespace as it is defined within the app itself, and we can invoke the setter via an appropriately named attribute. This only takes a single parameter – the URL, and a reference to the ImageView object will get added to the call to our custom setter. It’s also worth noting that no additional variables were required in the <data> section of the layout – not even the Glide instance that was required by the earlier, broken example.

That’s it, if we run this we automagically get our images loaded:

Part3

Although that’s quite a lot of explanation, the amout of code we actually needed was minimal – the few lines of the custom setter, and an additional two lines in our layout! In the next article we’ll look at how we can use Data Binding in an even more dynamic way.

Since originally writing the code for this article a couple of other people have done some interesting things with custom setters. At Droidcon NYC Roman Nurik presented some virtually identical code to mine for image loading using Glide. My code was inspired by the example in the official Data Binding GuideData Binding Guide – specifically the section on custom setters which explains image loading using Picasso. It seems an obvious move to adapt that example to use Glide instead of Picasso and I’m sure that Roman and I are not the only people to independently produce almost identical code from that example!

A really interesting idea from Lisa Wray is to use data binding custom setters to apply custom typefaces to your TextViews. Lisa’s example perfectly demonstrates how we can use custom setters to add behaviour to stock View components without having to create a custom View. This is powerful stuff!

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.

1 Comment

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.