GitHub LinkedIn Feed

Drawables in Null-Safe Models

Jul 6, 2015
Table of Contents

Storing an image inside of a model is as common as can be, however if that picture can take on local and remote guises, you may find yourself repeating the same conditional statements each time you display it, thus cluttering up your view logic. This article will describe a possible solution using a DrawableSource pattern relying on the “never return null” principle.

Motivation

Consider a banking app, where we have an Account model that we have to display in a list with its name and a title graphic. The graphic can either be a specific image returned from an API, or one of the default resource images based on the account name.

Solutions

Here are some progressively less naive solutions.

Storing a Drawable

One option is to generate and store a drawable, regardless of whether it came from a downloaded bitmap or a local resource.

public class Account {
    String mName;
    Drawable mImage;
}

That’s insane: drawables are context-specific and are neither serialisable, nor parcelable, so you cannot persist the class anywhere.

Bitmaps are slightly better as they are at least parcelable, but they are large and inflexible, so if you persist the model to disk, you would be wasting disk space by potentially storing a local resource repeatedly.

Storing the URL

Another option is to store the URL separately and apply the local resources in the getter.

public class Account implements Serializable {
    String mName;
    String mImageUrl;

    @Override
    public Drawable getImage(Context context) {
        if (mImageUrl != null) {
            return downloadImage(mImageUrl);
        }
        return context.getDrawable(getCurrencyImageForName(mName));
    }
}

Slightly better, except now we need a downloadImage(String) method and since network calls cannot run on the main thread, that whole getImage(Context) must be invoked from a background thread — not ideal.

Storing a Universal Container

We can improve the last attempt by creating a universal container for any image source and easily passing that around.

public class DrawableSource implements Serializable {
    private String mUrl;
    private int mResId;

    public DrawableSource(String url) {
        mUrl = url;
    }

    public DrawableSource(int resId) {
        mResId = resId;
    }
}

Then getImage() becomes (notice that it no longer needs a context):

@Override
public DrawableSource getImage() {
    if (mImageUrl != null) {
        return new DrawableSource(mImageUrl);
    }
    return new DrawableSource(getImageForName(mName));
}

This solution is more elegant, serialisable and null-safe. One problem though: to apply the image to an image view, you still require the boilerplate conditional statement to decide whether you need to download the image or retrieve it from local resources.

Applying the Image

Now that we have separated out the image logic into DrawableSource, the model no longer cares about the type of image it carries. As a result, we can contain the boilerplate conditional statement within the utility, without affecting the models.

public class DrawableSource implements Serializable {
    ...

    public void applyTo(Context context, ImageView imageView) {
        if (mUrl != null) {
            Picasso.with(context).load(mUrl).into(imageView);
        } else if (mResId != 0) {
            imageView.setImageResource(mResId);
        } else {
            imageView.setImageBitmap(null);
        }
    }

    ...
}

Here I’m using Picasso, but you could use any other library to download images.

In fact, you could even supply a function (anonymous class) implementing the DrawableLoader interface below. When going down that route, it would be wiser to store that function as a constant to simplify any future refactoring, if you decide to one day migrate to, say, Glide.

public interface DrawableLoader {
    void onLoadImage(Context context, String url, ImageView imageView);
}

Conclusion

It may seem like this simple pattern is an insignificant improvement over the naive approaches, but let’s summarise what it achieves:

  • Null safety, even with an empty image
  • Separation of the loading logic (away from model and view)
  • Applying the image to an image view reduced to one line
  • Image in the model reduced to one field, regardless of its type

I considered turning this pattern into a tiny library, but ultimately decided against it: your data requirements will always change between projects (e.g. URL and resource types, special image views etc) and it’s not a good idea to abstract your loading logic into a “magical” black box. Therefore, it serves its purpose best as a repeatable pattern.

Source

  • android
  • mobile