CapTech Time Tracking for Android

As a consulting company, it's very important for CapTech employees to accurately keep track of time spent on projects for billing, reporting, and future planning purposes. To facilitate easy timekeeping, I joined a team in the Mobile Technologies group to develop a mobile version of Unanet, our internal timekeeping software. The project targets the three most popular smartphone platforms: Apple's iOS, Google's Android, and everyone's HTML5. I was assigned to oversee the user interface on the Android application, and I wanted to share some thoughts on the experience.

Android timesheet list screenshot Android timesheet detail screenshot Android day detail screenshot

Development Environment

Android runs on a modified version of Sun's Java virtual machine called Dalvik. Google provides a great Eclipse plugin for Android development that simplifies the otherwise complex project setup and emulator creation. I've had plenty of experience with Eclipse before, so everything in the development environment went very smoothly. My only frustration is having to register any Activity (basically a screen in the application) in the AndroidManifest.xml file before using it. It seems like you should automatically register any class that extends Activity.

Design Patterns

The Unanet application fits the drill-down model popularized by the iPhone SDK very well, but our API to the Unanet server was very coarse-grained, which was challenging to integrate efficiently into our layout. The differences between our local storage (SQLite) and the remote server would easily abstracted away using the ContentProvider framework, which is used throughout Android. The provider easily interfaces with our custom SyncAdapter, which in turn plugs into the Android Accounts & Sync system. It provides a standardized way of organizing user accounts, managing the network activity during a sync, and providing updates to the user.

Android drilldown screenshot

The ContentProvider also gives each piece of information its own URI, and any UI screens that display that information register a ContentObserver to react to any changes in it. The only problem with this setup is that the ContentObserver only gets a message that some data has changed, for example when a timesheet's project is updated, but you don't get an indication (or better yet, a URI) of which timesheet actually changed. Thus, the UI is forced to reload everything on any change, which carries a significant performance penalty. Continuing the observer pattern, the application implements a SyncStatusObserver that updates the "Last sync" footers at the bottom of each activity whenever a sync is started, currently in progress, canceled, or completed.

Android project detail screenshot

ListViews and ArrayAdapters were used as the base of every Activity and content is fed to them through the AsyncQueryHandler pattern. The pattern performs database queries in a background thread, which leaves the UI responsive in the main thread. However, sometimes this creates an undesirable flash of empty content in between the query's start and when the data is available. We could have gotten away with a blocking query because our dataset is relatively small; however, we wanted to use the recommended implementation.

Tips & Gotchas

Developing a PreferencesScreen was troublesome because any exception in the screen brings down the entire OS, not just your application. Once this happens a few times (and the accompanying 3-4 min. emulator boot time), you start being very careful about the code you write. Also, DialogPreference subclasses cannot be used on the main preference screen without causing an exception per bug #8350. To work around this, I ended up having to create a secondary screen which then contained a ListPreference, a subclass of DialogPreference.

Android preferences screenshot

Most of the layout is defined in XML files associated with each view, and I was very frequently tweaking them to get the UI just right. My instinct is to save the file and then rerun the application, but this won't work! Eclipse will try to execute the XML by itself, which generates an error and breaks your project. To fix it, you need to delete the .out file containing the error (usually its called myfile.out if you tried to run the app from myfile.xml), clean the project, and re-run from a .java file.

When following the AsyncQueryHandler pattern, most of your code ends up executing on background threads, and you'll need to use Activity.RunOnUiThread(Runnable) to update the UI.