SureshJoshi.com ▼

Couchbase Lite Attachments in Picasso


2014-11-18

While learning Android over the past year and a half, it’s become apparent to me that there are just a ludicrous amount of 3rd party libraries to pick and choose from. Like anything, they range from horrible, to incredible, and they can really make or break an app. In situations like these, I find it really helpful to come up with some shortcuts to speed up development time. My shortcut for Android? Do whatever Jake Wharton does.

Enter Picasso

While applying this philosophy to an app I was working on, I ended up using Picasso to handle image loading/caching. Picasso is pretty awesome, but when it came to image caching for an offline app, I discovered that the image caching part is largely based on OkHttp and the cache control headers coming from the web server. There’s nothing inherently wrong with this, but in the particular situation I was in, this wasn’t working.

The backend was largely controlled by Couchbase and Sync Gateway, and it was setup in such a way that the images were being served as Couchbase document attachments (through a Couchbase Server or Sync Gateway API). There is some debate in the Couchbase community as to whether this is a good approach or not (I’m erring towards it not being a good idea), but in any case it was a very fast way to store and serve images from the backend, as well as bring those images down to the Android/iOS app using Sync Gateway and Couchbase Lite replication.

Enter Couchbase Lite

On the iOS implementation of Couchbase Lite (also known as Couchbase Mobile), document attachments can be reached by a file pointer URL, which means most image loading/caching libraries can use a Couchbase Lite attachment naturally through their standard API.

This same idea falls over on the Android implementation of Couchbase Lite, because for some reason, the same API wasn’t made available (I started a topic on the Couchbase Mobile forum).

This left me in an odd boat:

  • I have images persisted to Android disk through the Couchbase Lite replication

  • I want to serve those images without hand-rolling a caching algorithm

  • I can use Picasso to pull them from the web (meaning I effectively download images twice)

  • I cannot control the cache headers on the Couchbase Server document attachments (meaning Picasso never caches them to disk)

This ended up really being a worst-case scenario all around, especially as the app was intended for offline use, meaning I needed those images cached to disk - but rolling my own caching algorithm is a recipe for disaster.

Time for a Custom Downloader

When the guys at Square designed Picasso, they left in provisions for a custom downloader object. Below I’ve included the code that I used for mine. It’s very simple, and uses the assumption that the attachment I’m interested in is actually labeled under “image”.

The other tidbit of code I needed, but left out of this because it’s very specific is that I ended up needing to retrofit URLs to include custom information about the document ID (as opposed to a HTTP URL).

public class CouchbaseDownloader implements Downloader {

  private Database database;

  public CouchbaseDownloader(Database database) {
    this.database = database;
  }

  @Override
  public Response load(Uri uri, boolean localCacheOnly) throws IOException {
    try {
      Attachment image = database.getDocument(uri.toString()).getCurrentRevision().getAttachment("image");
      InputStream stream = image.getContent();
      return new Response(stream, true, image.getLength());
    } catch (CouchbaseLiteException e) {
      e.printStackTrace();
    }
    return null;
  }
}

Dagger stabs me in the back

In the app, there was extensive use of Dagger for dependency injection. Again, no problem here, as this is pretty handy, however, based on the original, inherited architecture of the app, the Couchbase Lite database was not available at the same time that I needed to build the Picasso singleton - and following the Picasso recommendations, I wasn’t going to try working around the singleton.

The correct solution at this point would have been to re-factor the app into a ‘better’ architecture, however, that would have taken a couple of weeks (time I did not have). Instead, I came up with this ugly little hack that let me defer creation of Picasso to when I had a database available - wrapping a singleton with a singleton… It was a sad, sad day.

public class CBPicasso {

  private static Picasso singleton = null;

  // Using the same fluent API call as Picasso proper
  public static Picasso with(Context context, Database database) {
    if (singleton == null) {
      synchronized (CBPicasso.class) {
        if (singleton == null) {
          singleton = new Picasso
                  .Builder(context)
                  .downloader(new CouchbaseDownloader(database))
                  .build();
        }
      }
    }
    return singleton;
  }
}

But it worked.

Feature Photo credit: Mr. Velocipede / Foter / CC BY-NC