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 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
.
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.
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.
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.
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
.
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.