Robust and readable architecture for an Android app

24 April 2014

Since the early days of Android, I've been looking for a robust way to build Android apps, keep the IO operations out of the UI Thread, avoid duplicated network calls, cache relevant things, update the cache at the right time, etc... with the cleaner syntax possible.

This blog post won't give a precise implementation, but one possible way to structure an app with a good balance between flexibility, readability and robustness.

Some existing solutions

At the beginning of Android, most people were relying on AsyncTasks for long running processes. Basically: it sucked, there's already a lot of articles on this subject. Later on, Honeycomb introduced Loaders which were better for supporting configuration changes. In 2012, Robospice came out, based on an Android Service running in the background. This infographic shows how it works.

It's great compared to AsyncTask, but I still have some problems with it. Here's the average code to make a request with Robospice, in an Activity. No need to read it precisely, it's just to give you an idea:

Step 4 — An Activity which needs some data!

My solution is, like Robospice, based on a service, but not an Android one. A regular singleton object, shared accross the app. We'll see the code of that service in step 5. But right now, let's see how the Activity code looks like, because this is what I wanted to simplify the most in the first place!

One line to request the user, one line to express the fact we'll receive an answer for that request. Same thing for contacts. Sounds really good!

Step 5 — A singleton service

As I said in step 4, the service I'm using is not an Android service. I actually started with one, but I changed my mind. The reason is simplicity. Services are meant to be used when you need to have something running while no activity is displayed, or when you want to make some code available to other apps. That's not exactly what I wanted. Using a simple singleton allows me to avoid using ServiceConnection, Binder, etc...

There's many things to say here. Let's start with a schema to show you what happens when we called getUser() and getContacts() from the Activity. Then I'll explain the code.

You can imagine each serial is a thread.

What you see here is what I really like about this model; the view is immediately filled with cached data, so most of the time the user doesn't have to wait. Then, when the up-to-date result arrives from the server, the displayed information is replaced. The counterpart of this is that you need to ensure the activity can receive the same type of response multiple times. Keep it in mind while creating the activity and you'll be okay.

That's a lot of code for a single request! Actually, I exploded it to make it more explanatory, but it's always the same pattern so you can easily create helpers to make single-lined methods. For example getUser() would look like this:

By default, all @Background annotated methods are run in parallel. Two methods using the same serial are guaranteed to be run on the same thread, sequentially (ie. one after the other).

Running network calls one after the other may have performance impacts, but it's so much easier to deal with GET-after-POST kind of things with it, that I'm ready to sacrifice a little performance. Moreover, you can easily tune the serials afterward to improve performance if you notice anything. Currently in Candyshop, I use four different serials.

To conclude

The solution I described here is a draft, it's the basic idea I started with, a few months ago. As of today, I've been able to solve all particular cases I encountered, and I really enjoy working with it so far. There's a few other awesome things I'd like to share about this model, like error management, cache expiration, POST requests, cancelling of useless ops, but I'm really grateful you read so far, so I won't push it!

What about you? Did you find the design of your dreams, one that you enjoy working with on a daily basis?