Or... Supporting Apple Watch without the Kit/OS2.
Titanium 4.1 already supports linking a WatchKit Extension built in Xcode, and our upcoming September 2015 release adds support for linking and communicating with Watch OS 2 apps built using Swift. Of course we are also working on the ability to build an actual Watch OS 2 app in JavaScript using Titanium. But today I will show how you can support the Apple Watch without any of that using Titanium 3.4.0 or later.
What I personally use most on the Apple Watch are interactive notifications. Third party apps often are painfully slow, although I assume that will improve once they are able to run on the watch with Watch OS 2.
Interactive notifications have been available for phone and tablet since iOS 8 and Titanium 3.4.0, but proved to be even more useful on the Watch. Getting a notification about an incoming message is fine, but if I don't have to grab my phone to reply we are really talking — literally! I'd still rather use my phone instead of having to type or dictate on my watch, but interactive notifications are great for selecting a quick reply or actions like Mark as Read.
I've built a sample messenger app to show you how it works. It's a simple WhatsApp-like messaging app. Every time you send a message, a pretty dumb bot will reply with a random answer after 4 seconds. This allows you to move the app to the background or lock your phone to see what happens in different scenarios.
The repository includes screenshots of different types of interactive notifications.
NOTE: As you can see, action titles can contain emojis — yeah!
I tried to keep the app as simple as possible.
- You can find all settings in app/config.json
- In app/alloy.js I set some global constants and helpers and require
app/lib/notifications.js
. - The bootstrapping for both local and push (interactive) notifications is done in app/lib/notifications.js.
- The tab controlled by app/controllers/console.js displays log messages so you can read back what happened on the non-UI side of things.
- The actual chat is controlled by app/controllers/chat.js.
NOTE: There is a known issue that the ListView sometimes will not render. Simply restart the app when this occurs. The issue is fixed for the next minor SDK release.
Interactive notifications can be scheduled locally by the app itself or pushed.
In both cases we first have to set up the interactive notification categories and actions. We do this in the first ~150 lines of app/lib/notifications.js.
Since iOS 8 you need to use Ti.App.iOS.registerUserNotificationSettings to set the notification types (alert, badge, sound) for both local and push. We use the same method to set the categories which in turn hold the interactive actions.
A category may define two sets of actions. One of 1-4 actions for alert-style and Apple Watch notifications and another of 1-2 actions for other styles. If you don't set the latter it will simply take the first two of the first.
NOTE: Apple Watch will only display actions that can be processed in the background.
Apart from an identifier and title there's a few things you can set:
- activationMode: If set to
Ti.App.iOS.USER_NOTIFICATION_ACTIVATION_MODE_BACKGROUND
it will just let the app execute the action without opening the app for the user. - authenticationRequired: If set to
true
and only when the user is not selecting the action via his watch, he must authenticate first, even if the device is not locked. - destructive: Will display the action in red if presented on the lock screen or watch.
NOTE: Using
authenticationRequired:false
andactivationMode:Ti.App.iOS.USER_NOTIFICATION_ACTIVATION_MODE_FOREGROUND
together will still require authentication if the device is locked.
From line ~160 we listen for local notifications.
The notification event is fired for both regular and interactive notifications when the app is opened via a local notification, but only when the user has not selected an action. It also fires when the app was in the foreground while receiving the notification. There's no way to tell the difference, but in both cases we want to present the message to the user so he can respond to it in the app.
NOTE: When the app is in foreground iOS will never show the notification to the user. It expects the app to handle it.
A separate localnotificationaction event is fired when the user does select an action. It will have the notification payload as well as the category and selected action identifiers. You can see how we handle this around line ~190. Since we have push notifications as well, I choose to use an event dispatcher created in alloy.js and trigger a custom action
event with the required payload. I'll listen to this event in the console and chat controllers.
The sample app is prepared to receive interactive push notifications in lines 209+, but you will need to create an ArrowDB datasource and add your key in tiapp.xml for it to work.
When the user has responded to a push notification by selecting an action, the remotenotificationaction will be fired. We handle this in the same way as we did for local notifications.
The chat uses local notifications and the console tab has a button to send one as well. In app/controllers/console.js you can see how we use Ti.App.iOS.scheduleLocalNotification.
- alertAction: Should be a verb plus optional subject since it will used instead of the default Open for alert-style notifications and in Slide to [alertAction] on the lock screen.
- date: Optional date to schedule for or leave it out to send it now.
- category: If set, it must be the identifier for one of the interactive notification categories we registered.
- userInfo: Can be any object. Our sample uses it to pass the ID of the message which we need to handle the action.
To send an interactive push notification, use the Dashboard or API and including a valid category
value and optional payload.
I won't go into too much detail on how the chat works, but here's what app/controllers/chat.js does:
- It uses Alloy data-binding to render a messages collection in a
Ti.UI.ListView
. - It listens to the
action
event fired innotifications.js
to execute the action the user selected in onAction(). - When you send a message it will set a 4s timeout to add it to the collection and show the local interactive notification.
- Then finally there's some logic to update the app and tab badges and mark messages as read when you see or reply to them.
See the comments throughout the code for more details.
With Titanium 5.1 we've introduced the behavior property for actions. With just a few modifications in lib/notifications.js en the chat controller you can now reply with a custom message directly from the notification banner or by dictating it to your Apple Watch:
As a bonus, viewing a notification on your Watch automatically triggers handoff for the Titanium app. Your app icon will show in the bottom left of the lock screen and swiping up will open the app. However, consecutive tests using the sample didn't always get the same results for me and only in Titanium 5.0 and later will you be able to listen to the handoff event in your app.