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

Backgrounds

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.

Digits

I want to provide a simulated vacuum fluourescent display for my timer. I grabbed some 7-segment display images from the Wikimedia Commons. The 2000×3333 images are in the artwork/digits directory.

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:

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

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

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, notification, 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 Ti.Android.NotificationManager.notify().

Wrapup

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

Download the source code here. This includes a binary build of my modified AlarmManager module.

Part 1 · Part 2 · Part 4

Leave a Reply

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