This is the first in a series of posts about Droid-Fu, a shiny new shared library for Android application developers. In this post I will introduce some of the ideas behind Droid-Fu, and start by showing off some of its core features.
What is Droid-Fu?
- Droid-Fu is a general purpose Android application library, deployed as a JAR
- Droid-Fu’s primary purpose is to make your Android developer life easier
- Droid-Fu is open source! (fork it, hack it, share it, all on GitHub)
- ain’t that enough?
Droid-Fu contains utility classes as well as some self-contained, ready to use Android components, all of which I consider useful for typical Android applications. Some of the areas for which Droid-Fu offers support classes include:
- application life-cycle
- background tasks
- HTTP messaging
- (remote) image handling
- custom adapters
… and more. The library is still young, so expect to see a lot more stuff forthcoming!
Who uses it?
Droid-Fu contains mostly code which I pulled from Qype Radar, our geo-sensitive mobile application which let’s you find, review, and share all those cool places near you. In other words, Droid-Fu is used in a production quality app, and even though it’s a WIP, we find it to be quite solid. Of course you’re invited to make it even better. Did I mention it’s open source?
The Foundations
One of the strongest (in my opinion) benefits of using Droid-Fu are its application life-cycle helpers. If you are developing an application which performs background work, such as fetching data from the web, you will almost definitely want to Droid-Fu-idize your app. Here’s why.
That Dratted AsyncTask
I’ve written about this two times now. I think with this third approach, which made it into this library, I finally found a decent solution. But, let’s review the biggest problem with Android’s AsyncTask, first. What is AsyncTask? It’s Android’s helper class you’re supposed to use for performing expensive (that means, blocking) operations. If you’re writing an app that talks to a web service, you’ve very likely already used it. (You’re not doing all that work on the UI thread, do you? If you do, chances are your activity will only live for a couple seconds, because Android kills non-responsive activities. ANR, anyone?)
So the basic idea is: launch an AsyncTask making your service call, show a nifty progress dialog while the task thread is running, and have the task’s result be posted back to your activity once it completes. Cool, but what if the user decides to rotate the screen while your task is running? Or a phone call comes in, interrupting your app, and Android decides to kill it? Both these actions will effectively terminateyour activity, and recreate it when resuming (yes, a screen rotation kills your activity, very clever, isn’t it?). Unfortunately, any AsyncTask that was still running now holds a stale reference to your activity, because the restarted activity will be an entirely different object in memory (and it will go through onCreate(), as if the activity had started for the first time). I’m not entirely sure whether AsyncTask will actually post back the data to the old activity object (if it was a weak reference, it may already have been garbage collected), but in any case, your “new” activity will never see it, because it’s a different instance.
Now, one could argue: well, just do all the work again, like, re-send the request or whatever job was running. Yes, you could do that. But that’s wasteful and, really, makes you feel stupid, no? Plus, if the user triggers a web service request, then flips the screen, decides that this wasn’t helpful, and flips it back, then your request is being sent 3 times in parallel. Is that what you want? Probably not.
BetterAsyncTask to the Rescue
Thanks to Droid-Fu, there’s a solution to this: BetterAsyncTask! (I’m a lazy person, and I couldn’t come up with a better name). It behaves exactly like AsyncTask (in fact, it is an AsyncTask), but it does some extra work for you: first and foremost, it keeps track of the active instance of the context that launched it, and if that instance should change, it will post the data back to the new instance. In other words, you can dispatch your task, flip the screen back and forth mentally, and BetterAsyncTask will still post the task’s result back to whatever activity instance is alive at the time it finishes. In other words, the task is only ever run once, regardless whether the context in which it was launched died while it was running or not.
There’s a catch though. Since there is no way for BetterAsyncTask to figure out which one is the current instance of its activity, it relies on a helper: DroidFuApplication. This class derives from Application, and if you want to use BetterAsyncTask, your application has to derive from that class, otherwise it won’t work. That’s because DroidFuApplication keeps a hash of WeakReferences mapping contexts (activites and services) to their active instances, and when a BetterAsyncTask finishes, it will ask your application for the active instance.
This is all you have to do to launch a task showing the standard Android indeterminate progress dialog:
public class MyActivity extends BetterDefaultActivity {
protected void onResume() {
super.onResume();
if (isLaunching()) { // this is explained further down this article
MyBetterAsyncTask task = new MyBetterAsyncTask(this);
task.execute(someData);
}
}
}
It doesn’t end here though. BetterAsyncTask does other cool stuff, such as automatically opening and closing progress dialogs for you while it’s running, or triggering that little progress spinner in the activity title bar when your activity has enabled that feature. It also allows your task to throw exceptions in its worker method, which are then posted to an error handler you define. Some of that stuff, like spawning dialogs, only works if your activities inherit from BetterActivity, which I will get to now.
BetterActivity and BetterService: Pimp my context!
Droid-Fu provides base classes for your activities and services which provide some simple but very useful helper methods. Currently, there are only BetterDefaultActivity, BetterListActivity and BetterService, but I intend to implement the Better* stuff for all stock Android activity base classes (like MapActivity and all the others).
Here is what you get:
Life-cycle helpers
these methods let you conditionally perform work depending on a context’s life-cycle state.
- isLaunching() is true if and only if the context just went through onCreate() for the first time (but was not restored, i.e. onRestoreInstanceState() was not called)
- isRestoring() is true if your context is recovering after being killed by Android
- isResuming() is true if your context is “soft-resuming” as I call it, i.e. there was no call to onCreate() before going through onResume()
- isApplicationBroughtToBackground(): sometimes it’s necessary to distinguish between your activity being paused by another activity of your own application, or by an entirely different application. This method yields true if another application’s activity is hiding yours.
Dialog helpers
BetterActivity offers the following helper methods to easily let you spawn dialogs:
- showInfoDialog(): shows an alert dialog with an info icon
- showAlertDialog(): shows an alert dialog with a warning icon
- showErrorDialog(): same as alert dialog, but takes an exception as an argument
- showListDialog(): shows a dialog with a list of Ts (T is some generic type). When clicking an entry, it will call back to a handler with the T that was selected.
all dialogs are customizable (e.g. message or icon).
There is more to come!
There’s a lot more in Droid-Fu at the moment (will cover that in upcoming posts), and there will be even more in the future, since I’m continually working on this library.
Cool, where can I get it?
The Droid-Fu source code is currently hosted on GitHub, which is the place where the cool kidz hang out these days. It’s a maven project, but the JARs aren’t yet hosted anywhere. Anyway, just check it out using git clone:
git clone git://github.com/kaeppler/droid-fu.git
and run:
mvn install -DcopyTo=path/to/your/apps/lib/dir
(you need to install git and maven2 for all that magic to work).
Happy coding!