Gracefully Handle When Location is Unavailable (Android)

2010/12/24

This seemed like a useful pattern so I thought I’d document it here, as it seems quite common. The scenario is that you have an application that provides some location-based service or information, but needs to gracefully handle the situation when the location cannot be obtained.

Ther are various reason why the location might not be obtainable. The most common is that the user disabled one or all location services requested by your app. Just because you request location permissions and the user accepts doesn’t mean that they actually have those services enabled. And worse, when you request a location in this situation, you don’t get any sort of indicator that it has been disabled. Your location changed callback is just never called.

My application grabs earthquake data from USGS and provides information to the user with respect to their proximity to the quake. If the app can’t get the user’s location, it should functionally normally, minuses providing distances to quake epicenters. For reference, the app is called QuakeAlert! and you can find it on the market, and the source on Google code.

QuakeAlert! has a service that runs periodically and alerts the user of any new earthquakes that match their criteria. When the service runs, it should get the location if possible, process the data from USGS, and potentially alert the user. The problem is in how Android location callbacks operate. The service can request a location update, but there’s no guarantee it will ever be called (and it won’t if the user has disabled the location service).  Here is the solution,

UpdateService- this is an intent service that does the real work, whatever that may be. In the case of QuakeAlert! it fetches the data from USGS and processes it, sends notifications, and updates the user interface (if it’s running).

LocationService- this is a regular (non-intent) service that does the following,

  1. Registers for location updates
  2. Schedules a TimerTask for execution (say a few minutes in the future)

If the service gets an “on location changed” event before the time is up, it cancels the timer. If the timer runs first, it cancels the “on location changed” registration. In both cases, it calls the UpdateService to do the work.

UpdateService gets the location by calling getLastKnownLocation(). If there was a location obtained through the running of LocationService then it will get that location and use it. The last known location may have also been obtained by a different application at some earlier time getting location updates. That’s okay to, we know we’ve tried our best to get the most accurate location we can at the time.

The implementation of LocationService in QuakeAlert! is reusable. Just pass it a timeout (in milliseconds) and a broadcast intent to send when either the location in obtained or when the timer executes. For example,

// create broadcast intent that will ultimately start your
// intent service
Intent broadcastIntent = new Intent(...);

Intent locationIntent = new Intent(context, LocationService.class);
locationIntent.putExtra("timeout", 1000 * 60 * 2); // 2 minutes
locationIntent.putExtra("broadcastIntent", broadcastIntent);
context.startService(locationIntent);

Why pass a broadcast intent, instead of an intent that would start the update service directly? Since UpdateService is an intent service, we must obtain a wake lock before it is run, and the pattern for doing that is to receive an intent (in a receiver), grab the lock there, start the service from the reciever’s handler, then release the lock in the intent service when the work is done.

Note that this same pattern applied to a a foreground activity is much more straighforward: 1) register for location updates 2) open a cancelable progress dialog. If we get an “on location changed” event, dismiss the dialog, and start the UpdateService. If the user cancels, dismiss the dialog, and start the UpdateService. In both cases remember to remove thelocation listener.

Advertisements

3D Model Viewer for Android

2010/12/07

I just published a new application on the Android Market. It’s called ModelView and is a 3D model viewer. You can download it onto 1.6+ devices. It supports loading of standard .OFF and .OBJ format files, allows rotation and zoom (via multi-touch, on 2.0+ devices), and comes packaged with many interesting 3D models. You can also view your own .OBJ / .OFF files by placing them on the SD card.

This started out as a sample OpenGL project, with me learning how to define simple 3D objects with vertices and triangles. From there I started to think about how I could define the model’s data some other way than statically in the code. Then I started looking for standard ways to do that. Then getting lazy and wanting to take advantage of models defined by others.  I ended up learning quite a bit about OpenGL and 3D graphics. Now it’s time to get a book and put it all in it’s place. The app is really just a learning exercise and serves no real purposes, but it’s fun to play with so I posted on the market so other people can take a look. The code is available on Google code, and hopefully someone else can learn from this.