Hopefully, you’ve had a chance to review my previous post on Local Notifications as it sets the foundation nicely for taking the next step to scheduling notifications to occur at some point in the future – when the application itself may or may not be actually running.
for Scheduled Notifications
Time-specific “local” notifications
Taking the step from a local notification to a scheduled one is pretty simple; a scheduled notification is more or less a local notification with a specific delivery time attached to it. Given that there is no guarantee that the application will be running when that time arrives, a scheduled notification can actually fire when the application itself isn’t running.
Any time-management application is an obvious candidate for incorporating scheduled notifications, but don’t hesitate to think outside-of-the-box. Reminders may be equally valuable in many applications that don’t sport a calendar as the user interface; a financial management application, for instance, could alert you regarding upcoming tax or other filing deadlines.
Design Considerations for Scheduled Notifications
Badges? We Don’t Need No Stinkin’ Badges
Badges can’t be a target for scheduled notifications; only tiles and toast are supported.
To Schedule or Not to Schedule
In my previous post on Local Notifications, I mentioned that you can detect whether tile or toast notifications are enabled and perhaps decide whether or not to bother sending them. With scheduled notifications, there’s a bit of a twist, because the user's settings could change between the point the code schedules the notification and when it’s received.
As a result, notifications that do get scheduled may not appear because the user subsequently either opted out of tile updates via the app bar or turned off all notifications via PC Settings. Conversely, if your application uses the setting property to decide not to schedule a notification, say for a sports league practice application, you could confuse a user who subsequently opted in to notifications and might now be expecting to see a reminder.
There’s no harm in always scheduling the notification and letting the state of the system at the point the notification occurs determine what the user sees (or doesn’t). If it turns out that a notification that was previous scheduled, but has not yet occurred, shouldn’t be shown, you can programmatically remove it.
Does Anyone Really Know What Time it Is?
Whenever time is involved, things get complicated! What time zone? What about Daylight Savings Time, British Summer Time, and other similar wrinkles?
When you schedule a notification, the delivery time (and expiration time for tiles) that you specify is localized, but the underlying notification mechanism converts it to Coordinated Universal Time (UTC). The time of the notification is set at the point the notification is constructed, which yields a scenario like this:
- You’re in Boston on Monday, and your application schedules a reminder for Tuesday at 3 p.m.
- You fly to San Francisco Monday afternoon, and upon arrival update your system clock to reflect that you’re now on Pacific Time.
- On Tuesday, your reminder appears at noon, since there’s a time zone differential of three hours between Boston and San Francisco).
In most cases, this is what you’d expect and desire, but what if you want more of a locale independent reminder – you have medication you need to take at noon and would like to have a toast notification alert you at noon, regardless of what time zone you happen to be in? That’s takes a little more work because the notification time isn’t absolute but rather is dependent on your local settings. Here’s one way you could go about this:
- Schedule the notification normally; it will occur at noon (“wall time”) as long as you don’t change the system clock.
- Store the notification id, the scheduled time for the notification (noon), and the details of the notification (perhaps even the full XML template) in application data storage.
- Setup a background task that will run when the time zone changes on the device. (For more information on background tasks, check out the whitepaper and a sample at dev.windows.com which includes a background task specifically tied to the timeZoneChange SystemTriggerType.)
- In that background task,
- Loop through all of the scheduled notifications (using getScheduledTileNotifications or getScheduledToastNotifications),
- See if the notification is dependent on wall time by checking if the id of the notification is in the list of notifications you stored earlier, and
- If there’s a match, the notification is wall-time dependent, so remove it from the schedule and schedule a new notification using the information saved when it was first scheduled.
It’s Déjà vu All Over Again
You can schedule up to 4096 notifications for an application, so for recurring tasks – say a reminder to fill out a timecard every Friday – you could just schedule a year or so worth of reminders ahead of time and then maybe include a separate “meta-reminder” in a year to schedule the next year’s worth, but there’s a much better way!
With a MaintenanceTrigger you can initiate a background task to run at a regular interval, let’s say every week. In the background task itself, you’d then schedule the next weekly reminder. There are a few constraints to be aware of though:
- The system has up to 15 minutes from when the freshnessTime elapses to schedule the background task, so leave an adequate buffer to ensure the notification gets scheduled before it should actually occurs!
- A background task associated with a MaintenanceTrigger never fires when the machine is on battery power. The triggered tasks are, however, buffered so they will occur the next time the machine is plugged in.
change! Be sure to include a mechanism in your app for cleaning up notifications that are no longer relevant such as a reminder for a meeting that’s been cancelled. Consider using the id property to link a notification with a content item if changes to that item may require modifying the notification. An id is a 16 character string of your choosing giving you the flexibility to devise an encoding scheme for such a mapping.
Finding a notification with a given id requires traversing the list of notifications and then checking each of the notification ids against whatever encoding scheme you’ve devised. Matching notifications can be easily deleted via TileUpdater.removeFromSchedule or ToastNotifier.removeFromSchedule.
Constructing the Notification
You’ll use one of two classes for the notification (depending on the notification type, toast or tile). Each of the class constructors expects two parameters: a completed notification template (see my prior post for a primer) and the localized notification delivery time.
- id is used as a key to identify a given scheduled notification in the list of pending notifications; it’s a string of your choosing of up to 16 characters that you’d use to find a to-be-delivered notification for deletion or modification.
- deliveryTime (readonly) is the time the notification will occur, stored as Coordinated Universal Time (UTC) but set using local time semantics when the notification instance is constructed.
Each ScheduledToastNofitication likewise includes id and deliveryTime properties, but it also offers ‘snooze’ settings to repeat the notification at a defined interval:
- maximumSnoozeCount is the maximum number of times the toast will appear – up to five – and is set when the notification is created,
- snoozeInterval is the amount of time that should elapse between each showing of the toast (up to the maximumSnoozeCount), the range is from one minute to one hour, inclusive, and is also set when the notification is created.
Scheduling the Notification
Once the scheduled notification instance has been constructed, it’s a simple call to the addToSchedule method of the appropriate notifier class, either ToastNotifier or TileUpdater. Remember, badges can’t be updated via scheduled notifications.
For recurring notifications, as mentioned above, you may want to leverage a MaintenanceTrigger to schedule the next notification in the sequence. Full coverage of background tasks is a bit out of scope for this post, but the SDK background task code sample and whitepaper should provide insight into how to use tasks in conjunction with notifications.
Clearing the Notification
After the scheduled notification has arrived, the same semantics for clearing it that apply to local notifications apply here. Specifically, you can reset (or more technically clear) a tile to the state specified in the application’s manifest, and you can programmatically hide a toast, keeping in mind that the guidelines discourage doing so.
More interesting (and challenging) is clearing notifications that are scheduled to occur but should be removed since they are no longer relevant. I’ve already broached that subject in the design considerations section above, but here’s a itemization of the key API classes and properties you’d leverage to do so:
- the id property of a ScheduledToastNotification or ScheduledTileNotification can be set to an meaningful string (of up to 16 characters) to get a handle to a notification that might be modified. If needed, you could use that id value as a unique index to a persistent data store that records additional context about the notification.
- use the removeFromSchedule method (on ToastNotifier or TileUpdater) to cancel a pending notification that you’ve identified should no longer be delivered.
In more sophisticated scenarios, when you’ve engaged a background task to set up a recurring toast or tile notification, you’ll need to cancel the background task as well. I’d recommend doing this first and then cleaning up any notifications that may have been scheduled as a follow on step. To disable the background task, you’ll leverage a few of the methods and properties of BackgroundTaskRegistration:
- allTasks is a collection (a static dictionary to be exact) of the background tasks that the application has registered, via a call to BackgroundTaskBuilder.register
- when the background task to be cancelled has been identified, pass its reference to unregister along with a Boolean parameter indicating whether you want any currently running instances of the task to be aborted as well.
Check out the Scheduled notifications sample on the Windows Dev Center; it includes functionality for the snooze feature as well as querying and removing notifications that are scheduled for the future (but have since been deemed obsolete).
Although the Background task sample doesn’t deal with notifications directly, it provides good insight into the patterns and practices for setting up recurring tasks tied to system event, like a time zone change. I’d also recommend reading the Background task whitepaper for more insight into this important facet of a Window 8 application’s lifecycle.