You’ll notice that it has been over 6 weeks since my last installment. There’s a reason for that. Trying to build a seemingly simple app like a countdown timer has proven to be insidiously difficult.
Imagine building a countdown timer as a desktop app or in a web page. It would be extremely simple to do. You periodically update the time displayed on screen, and you check to see if the timer has hit zero. When it hits zero, you play back a sound. Easy, right?
In a mobile app, however, the rules are very different. Your app may not be in the foreground when the timer hits zero. Your app may not even be running when the timer hits zero. The device may not even be powered on when the timer hits zero. You have to deal with all these scenarios. And to make matters worse, if you’re building a cross-platform Titanium Mobile app, you have to deal with the nuances of background processing, notifications, and alarms on both iOS and Android.
First, tie up some loose ends…
When we left off, we’d built the cooking temperature reference tab. We provided support in our data structure for adding the ability to use USDA or chefs’ (less conservative) cooking temperatures. We just need some UI support.
On the TempDetailWindow, we slide up the TableView to make room for a new button, styled with a gear icon.
We add support to our
script to process
gear.png similarly to the way we process the stopwatch and thermometer icons that
we use on our tabs.
When this button is clicked, we display a
Ti.UI.OptionsDialog. See screenshots for iOS and android:
When the user makes a selection
from this dialog, we save the setting so that it is persistent by using
Ti.App.Properties.setInt ('option_temps_source', e.index);
load_data() function knows to look at the
option_temps_source to determine
whether we should show USDA or chefs’ temperatures.
Now it’s time to move on to the real meat (pardon the pun) of the application – the timer itself. The objective is to build a multi-stage timer that is easy to set and easy to use while your hands are busy with the grill. If I’m grilling steak, for instance, I might want to grill four minutes, turn, grill four minutes, flip, grill four minutes, turn, grill 4 minutes, then done. I’ve used the timer built into my Samsung android phone, and it’s OK, but at each stage, you have to unlock the phone, reset the timer, and start it up. That can be a lot to mess with when you’re busy turning the cuts of meat on the grill.
This timer will automatically run for a specified number of segments, alerting at the end of each segment and automatically starting the next segment.
First, let’s get into the aesthetics of the app. Sometimes I like to start with that because it’s often the most gratifying part of building an app — seeing pretty visuals.
Here’s what it looks like on iOS and android:
In part 2, I used a semi-opaque layer to screen back the background images. But this really adds
more work for the mobile device, which then has to composite the images. It’s not too hard to apply
a filter to the images before I packge them in the app, and this will reduce the work done at runtime,
and it will simplify our UI code by removing an unnecessary view. I modified
to modify the brightness of the background images.
As I started exploring orientation changes, I found that the TitanUp library needed some modifications
to better support orientation changes. The problem was that it was retrieving the width and height of
the display at initialization. An orientation change did not trigger a refresh of those values. So
TU.Device module now listens to the orientationchange event and updates the
displayWidth and displayHeight values as well as physical dimensions.
I want to provide a
simulated vacuum fluourescent display for my timer. I grabbed some
display images from the Wikimedia Commons. The 2000×3333 images are in the
I used Gimp to alter the images to give the lit segments a blue-green hue. In a real VFD display, you can see traces of the unlit segments. So for a natural look, I kept the unlit segments, but gave them low opacity so that they are not very prominent, and artwork behind them will show through.
I attempted to add a glow to the lit segments, but I wasn’t very successful at that. This is where I stop and realize why I’m a software developer, not a graphic artist. But even without the glow, the digits looked pretty good.
Now we need to prepare the digits for inclusion in the app.
It would be highly inefficient to have the mobile device converting a 2000×3333 digit image to the
appropriate size for the platform. So we use
convert_digits.php to convert the
image down to a more reasonable 400×666. If performance became a big problem, we could provide different
versions for different screen densities. But as I found later, this was not a big issue, so I opted to
keep it simple with one size of the image for all platforms. 400×666 gives us enough pixels to use on
tablets and still have a reasonable resolution.
How big will the digits be on the device? We’re going to use a dynamic layout to try to maximize the size so that they’re extremely easy to read. Since our timer window is only doing one thing, it may as well use all the screen real estate it can to display the UI.
In portrait mode, we want to display the time remaining and the number of segments remaining like this:
(where X is a digit)
In landscape, we’ll use the screen space a little differently:
So as we lay out the UI, we determine whether we’re in landscape or portrait, and from that we compute the width of a single digit like this:
_wLED = parseInt ((TU.Device.getDisplayWidth() - 2 * margin) / 4.25);
_wLED = parseInt ((TU.Device.getDisplayWidth() - 2 * margin) / 6.75);
On a Motorola Xoom (an HDPI tablet display), these amount to 179 pixels for portrait, and 183 for landscape. So our 400-pixel image is more than large enough to display on a tablet without upscaling. Even if the device had double the resolution, we would still have enough pixels to work with.
Three buttons are created for play, pause, and reset. Each is enabled/disabled in specific situations.
|Timer state||Play Button||Pause Button||Reset Button|
To add to the look of the VFD display, the buttons use simple blue-green icons with
alpha transparency as background images. If you open one of these images (for example,
play.png), you’ll notice a lot of padding around the icon. This lets us
make a button that is large enough to easily click.
When the buttons are disabled, we reduce the opacity of the button. A utility function is defined for this:
function setButtonEnabled (btn, enabled)
In the interest of keeping the display free of clutter, I decided to make the adjustment of the timer be gesture-driven. When the timer is reset, the user can touch a digit and drag up or down on it to increase or decrease the value. This allows the user to set the length of each timer segment and set the number of segments. These adjustments can only be made while the timer is initialized (to prevent an accidental change while the timer is running).
We attach event listeners to the
of the views that hold the VFD digit ImageViews.
If you take a look at
onTimeTouchMove(), you will see that we compute the
change in value by comparing the distance dragged to the height of the VFD digits. We increment
the value once for every 25% of the VFD height the user drags.
Here’s where I lost a few weeks of my life (keep in mind I do this in my spare time, so we’re probably talking about 30-40 hours of experimentation to get this all working).
Getting a basic countdown to work while the app is running in the foreground is no
big deal. I just use
setInterval() to trigger a function every 100 ms. In
that function, we compute the time remaining, and then we set the digit images (only
changing the images that have changed, because it would be too computationally expensive to change out
all 6 ImageViews’ images 10 times per second!).
When the time remaining on a segment hits zero, we advance to the next segment and we play a sound (my final solution doesn’t play a sound, for reasons we’ll discuss shortly).
This code works wonderfully if the device remains on and your app remains in the foreground. But since we intend for this timer to run for minutes at a time (possibly hours), there’s very little chance that the device will remain on and the app will be in the foreground the entire time.
Let’s look at what could happen on the device:
- screen times out due to inactivity (our app runs in the background, then is quickly suspended)
- user switches off screen (our app runs in the background, then is quickly suspended)
- user double-clicks “home” button and switches to another app (our app runs in the background, then is quickly suspended)
- user hits “home” button to return to the home screen (our app runs in the background, then is quickly suspended)
- user returns to the home screen, then uses the multitasking bar to kill our app (our app is stopped)
- screen times out due to inactivity (our app runs in the background for a while, then suspended)
- user switches off screen (our app runs in the background for a while, then suspended)
- user hits “home” button (our app runs in the background)
- user changes to another app via a task switcher (our app runs in the background)
- user hits the “back” button from our app (our app is stopped)
- user leaves our app, and operating system decides to terminate our app to free up needed resources (our app is stopped)
In any of these situations, we cannot guarantee that our app keeps running, so we can’t guarantee that we can sound an alert at the end of a timer segment! We’re going to need to do more to make our timer robust. The solution we choose depends on the platform.
iOS: Local Notifications
iOS has the concept of
Local Notifications. A local notification can be scheduled by your app
to alert the user at a specific time, whether or not your app is running or even whether the
device is sleeping or not. A local notification can put an icon and a message into the
device’s notification bar:
or activate the screen if it is asleep:
In Titanium Mobile, we use Ti.App.iOS.LocalNotification to set up and schedule a local notification.
So to facilitate the desired behavior in our timer, we can do the following:
- When the user starts the timer, schedule one local notification for each timer segment
- When the user pauses the timer, cancel all notifications
- When the timer is started or paused, use Ti.App.Properties to save the current state
- When the app is launched, check for saved state and resume the timer as appropriate
Here are a couple of things that I learned while experimenting with local notifications on iOS.
- notifications don’t show up visually or play sound if they fire while app is in the foreground
- notifications fire the
Ti.App) if the app is in the foreground
- notifications do not fire the
notificationevent if the app is not active; instead, they will fire three events when the app comes back to the foreground:
resumed(no matter how many notifications occurred while the app was not in the foreground)
Needless to say, these behaviors required a bit of code-around. We have to manually play the sound
if we’re in the foreground when the
notification fires. And we have to be smart enough
to not play the sound if the
notification event is firing due to a notification
occurring while our app was in the background.
GOTCHA: Notification sounds do not work on the iOS simulator. They do work on a physical device. I wasted a lot of time trying to figure out why my sounds weren’t playing. I even tried converting my .mp3 files to .caf files (you don’t need to do that).
All this said, working with local notifications on iOS to build a persistent timer was a piece of cake compared to solving the same problem on android.
android: AlarmManager + Notifications
Android also has the concept of
notifications, and they
are supported by Ti Mobile. A notification can put a message up in the status bar:
and put information into the notification drawer:
The Titanium guides even have a section on how you can use a service to schedule future notifications. It’s nice and all, but it just doesn’t work. I take that back. It works great if your phone never goes to sleep. But how realistic is that for our app? Really, how realistic is that for any app that needs to schedule future notifications? Unless by “future” you mean “a second or two in the future”.
I really tried hard to get the “future notifications” technique to work. I tried two approaches to my interval service:
- a service that fires every 500ms, checks the elapsed time, and notifies as appropriate
- a service that fires once and uses setInterval() to check the time periodically, notifying as appropriate
I had to look beyond the Titanium Mobile core to a third-party module by Ben Bahrenburg: benCoding.AlarmManager. This module exposes the native android AlarmManager interface. Android distinguishes between a notification, which is simply the presentation of information in the notification area of the device, and an alarm, which is a scheduled action that can happen at some specified future time, regardless of whether your app is running at the time or not. Using AlarmManager is the only way to get android to reliably alert the user at a future time.
benCoding.AlarmManager is a nicely built, well-documented module. But it wasn’t quite right for my application. It only allows the developer to schedule an alarm with granularity down to 1 minute. Very nice for making an alarm clock to wake up in the morning, but not so good when you want to make sure that you grill that nice steak for exactly 6 minutes per side! Luckily, the problem was fairly easily solved. I forked Ben’s project and created a version with an additional parameter, allowing me to specify the alarm time in seconds from the current time.
Some of the work I did with services in my first attempts to use the future notification technique paid off, as I ended up using an “AlarmService”, which at a specified time, will launch a service that I define.
The service is defined in
Resources/android/BackgroundNotification.js. (note that
it needs to be referenced in
tiapp.xml; see below). The service name we pass to
AlarmManager must match what is in the
AndroidManifest.xml. You can get the name one
of two ways:
tiapp.xml, and build once, then copy from the resulting
It is critical to remember that android services in Titanium are all of the type “interval”, so if you don’t stop it, it will be launched again when the interval expires. If you do this in your service code:
var service = Ti.Android.currentService;
var serviceIntent = service.getIntent();
you can ensure that the service runs only once. To make sure that you don’t inadvertently get another instance of the service code running while the first instance is running, you can set the interval sufficiently high when you create the alarm service:
Don’t be tempted to omit the
interval parameter, as you may encounter a NullPointerException.
The native AlarmManager acquires and holds a wake lock so that your code can run without having
the device go to sleep. You don’t have to explicitly request the wake lock. However, you do
have do declare WAKE_LOCK as a permission in your android manifest.
Here is what I added to my
<service type="interval" url="BackgroundNotification.js"/>
<uses-permission android:name="android.permission.WAKE_LOCK" />
In testing, the alarms are very reliable, even when the intervals are long.
GOTCHA: Titanium has a bug with respect to android notifications;
custom sounds will not play (see JIRA issue).
In my app, I work around this by explicitly playing the sound before calling
At this point, we have a working timer on iOS and android. The UI is nearly identical on the two platforms. It’s what we all dream of as mobile developers: a cross-platform app with 100% feature parity built with a single codebase.
That said, there are a few shortcomings that I should point out:
- when a notification fires from an alarm-driven service on android, the screen does not wake up. It might be nice to have that one extra visual cue.
- the sound volume is driven by android’s media volume setting. Unfortunately, while the app is running, the hardware volume buttons control the ringer volume, not not the media volume. So to adjust the volume for GrillTime, you have to open another app, like a music player. Not exactly convenient.
- the sound is not very loud on either platform — actually, that’s my fault, as I got tired of the first obnoxious sound I chose, and chose this more subtle alert sound. Ideally, we should probably allow the user to select from multiple sounds.
- the sound plays only once, which might not be enough to get the user’s attention. That could be solved pretty easily on android, since we play the sound explicitly in the code, but would be harder to solve on iOS.
Download the source code here. This includes a binary build of my modified AlarmManager module.