共用方式為


Alarms and Notifications in Mango

Here's the first of several posts about the new multitasking features in Windows Phone Mango. Alarms and Notifications is a good place to start because it is the simplest of the new multitasking features (outside of Fast App Switching, but that's not really an API) and it doesn't require running code in the background; it just leverages a system service to notify the user when a specific time is met.

If you haven't seen it yet, you can watch my Mix 11 video (or the Tech Ed version, which includes more conceptual stuff and less code).

The sample application is a very basic "todo" application that is impossible to build on Windows Phone 7 due to the need to display a reminder at a future point in time (at Mix the example was a cake-baking app :-) ). It isn't very functional and it isn't very pretty, but it illustrates the core concepts needed to use the Alarms and Reminders service.

Here are a couple of screenshots

reminder_screen_shot  reminder_details_screen

The code is provided below in a ZIP, but let's look at the salient bits of code.

Setting up the reminder

When you click the "remind me!" button in the item detail screen, the following piece of code runs (where item represents the ViewModel item that is bound to the page instance):

 // Setup the reminder
Reminder r = new Reminder(item.Title);
r.Title = item.Title;
r.Content = item.Description;
r.BeginTime = DateTime.Now.AddSeconds(15); // item.DueDate;
r.ExpirationTime = r.BeginTime;
r.NavigationUri = NavigationService.CurrentSource;
 
// Add it to the service
ScheduledActionService.Add(r);

Reminder is a new type in Mango that represents a possibly-recurring reminder that behaves just like the calendar reminders built-in to the phone (you can also use an Alarm, which is similar to a reminder but has slightly different behaviour - you should experiment with it and explore via IntelliSense).

The constructor takes a string which is used as the Name for the reminder. This lets you locate the reminder later on to check if it has expired or not, to remove it if it's no longer necessary, and so on. Note that you can create many reminders - in this example, one for each ToDo item.

The Title and Content properties are used to populate the text in the reminder when it pops up on-screen, like this:

reminder_popup

Note that you do not have control over the Snooze options, nor whether or not the "snooze" and "dismiss" buttons appear - they are all controlled by the system.

The BeginTime is the time that the reminder first goes off (in this case, we set it to 15 seconds from now so that it is easy to demo), and if you set the RecurrenceType of the reminder (not used in this sample) it also determines the beginning of the reminder pattern (daily, weekly, etc,)

The ExpirationTime is the time that the reminder should stop being scheduled. In this example it doesn't affect the UI (since we are not creating a recurring reminder) but it will be used later on in the code to determine whether we should clean-up old reminders or not. In general you should set this for all reminders since it will make your code easier.

The NavigationUri specifies the page to launch if the user taps on the reminder title / content when the reminder fires (little-known fact about WP7 is that this works even for the built-in calendar app, but only if the phone isn't currently locked). If you don't specify a URI, your app will just launch normally. Note that if your app is currently in the foreground when the reminder is fired, tapping on the title / content will dismiss the reminder but will not cause a navigation (since your app is already running).

Finally we add it to the ScheduledActionService, which will do the job of firing the reminder when the BeginTime is reached (note that the reminders are only accurate to the nearest minute, and in the beta the emulator gets less accurate over time... just close and re-open it if you find reminders are not firing at all).

Cleaning up the reminder

It's just as important to clean things up as it is to create them! The sample uses a simple helper method to do this every time a detail page is navigated to:

 // Removes the reminder if it has expired
void CleanupOldReminder(ToDoItem item)
{
  var reminder = ScheduledActionService.Find(item.Title);

  // If found and it has expired, remove it
  if (reminder != null && reminder.IsScheduled == false)
    ScheduledActionService.Remove(reminder.Name);
}

Now we see why setting ExpirationTime is important - it let's us check the IsScheduled property to know whether to delete the reminder or not, using the aptly-named Remove method.

The only other useful thing in this sample is how it deals with the deep links into the application from the reminders. As I've blogged several times before, Windows Phone uses the hardware Back key to navigate to the previous "place", and in the case where the user navigates directly into the details page (as is the case if they tap on the reminder) then the previous place will not be the main screen of the application, but rather whatever the user was looking at before (another app, the start menu, etc.). To see how this works on the phone, you can create deep links in the built-in apps and note that when you tap on the tile and then hit back, you return to the start menu. Examples of built-in pinnable items are contacts, albums, and playlists.

Nevertheless, some applications will benefit from enabling the user to navigate from the deep-linked page into the main UI of the application (none of the built-in apps do this, but you can). In this case, after a reminder has fired in the ToDo app, maybe you want to see what other things are left on your list. To do this, we introduce a "home" button on the detail page, but only display it if the Back key won't work:

 // Only show the GoHome button if there is nothing else on the backstack (ie, this is the first page)
if (NavigationService.CanGoBack != true)
  goHomeButton.Visibility = Visibility.Visible;

Here we check CanGoBack - if it is true, that means the user is navigating the application normally and the Back key will take them home. But if it is false, that means they deep-linked into the page from the reminder and can't use the Back key - instead we'll forward-navigate home. In order to avoid having the detail page on the back stack (which might be confusing), we'll signal to the main page that we want it to clear up the backstack:

 // Navigate to the home page, and tell it to clear the stack
void GoHomeClick(object sender, RoutedEventArgs e)
{
  NavigationService.Navigate(new Uri("/MainPage.xaml?clear=yes", UriKind.Relative));
}

Then inside the main page, we use a new method RemoveBackEntry to clean up the old entry if it's been requested by the calling application:

 protected override void OnNavigatedTo(NavigationEventArgs e)
{
  // If this is a new navigation AND the caller has asked us to remove them from the stack, then do so
  if (e.NavigationMode == NavigationMode.New && NavigationContext.QueryString.ContainsKey("clear"))
    NavigationService.RemoveBackEntry();
}

Also note that we've added the NavigationMode property on to all the navigation-related EventArgs, making it easier to do things like this (and manage animations, etc.)

And that's about it!

BackgroundNotificationServiceDemo.zip

Comments

  • Anonymous
    May 28, 2011
    Hi Peter, I think something wrong with the zip file, I am unable to unzip it.

  • Anonymous
    May 29, 2011
    Sorry about that - I uploaded again and it should work now.

  • Anonymous
    May 31, 2011
    Hi Peter, thanks for your post! It is - as your previous posts too - easy and pleasent to read and understand, even tough some things are not so obvious (e.g. thanks for mentioning the DeepLink and the missing home page of the App on the NavigationStack). Looking forward for your next post:-)