Building a Ti Mobile App: GrillTime, part 3

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 artwork/convert_icons.php
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:
OptionsDialog on iOS
OptionsDialog on android

When the user makes a selection
from this dialog, we save the setting so that it is persistent by using Ti.App.Properties:

Our load_data() function knows to look at the option_temps_source to determine
whether we should show USDA or chefs’ temperatures.

Keeping time

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.

User interface

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:

TimerWindow on iOS
TimerWindow on 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 convert_backgrounds.php
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
TitanUp’s 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 artwork/digits

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

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:

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.

User input

Three buttons are created for play, pause, and reset. Each is enabled/disabled in specific

Timer state Play Button Pause Button Reset Button
Initialized enabled disabled disabled
Running disabled enabled disabled
Paused enabled disabled enabled
Complete disabled disabled enabled

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:

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 touchstart and touchmove events
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.

The timer

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

Let’s look at what could happen on the device:

  • iOS:
    • 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)
  • android:
    • 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:

Local Notification on iOS

or activate the screen if it is asleep:

Local Notification on iOS

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 notification event (via Ti.App) if the app is in the foreground
  • notifications do not fire the notification event if the app is not active; instead, they will fire three events when the app comes back to the foreground: resuming,
    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:

Notification on android

and put information into the notification drawer:

Notification on android

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

Neither approach worked. Either way, the service will stop at some point after the screen goes
to sleep. Appcelerator support told me that I needed a WAKE_LOCK, but I’m not sure you can
obtain one via the core API.

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:

  • create the service javascript file, add it to tiapp.xml, and build once, then copy from the resulting AndroidManifest.xml in build/android
  • combine the Application ID with the name of the javascript file (sans “.js”), and affix “Service” to the end

GrillTime’s service name is com.smorgasbork.grilltime.BackgroundNotificationService

When the Alarm fires, the service is launched and the code in your service’s javascript
file starts executing.
The service fires the notification and schedules another AlarmService at the next appropriate
interval. As it is currently written, the AlarmManager module will not allow you to schedule all
the alarms at once, the way we schedule the local notifications on iOS. But on the flip side, we get
to run code at each interval, which is ultimately a much more powerful model.

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:

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 tiapp.xml:

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.

Part 1
Part 2
Part 4

Leave a comment

Your email address will not be published. Required fields are marked *