This is the 6th article in a series of articles designed to introduce iOS and Android developers to C#, Silverlight, and Windows Phone 7 application development.
One theme that you might see emerging as we go through this article series is that mobile phone users are constantly demanding more features, functionality, and connectivity from their devices. In the previous article we covered how users basically expect that their phones will know where they are on a map at any given time and be able to provide users with geolocation features.
Smart phone users also expect that when important things happen within the domain of any given application, that application should react accordingly. This can be everything from being notified when weather alerts occur nearby to being able to react immediately when something important happens in a real-time multiplayer game.
In this article, I’ll talk about push notifications and something that has no equivalent on the iPhone – application tiles. Push notifications allow some infrastructure out on the public internet (which some people have lately taken to calling “the cloud”, but that’s a pet peeve for another article entirely) to send messages directly to individual users of your application on a specific device.
Review of Apple Push Notification Services (APNS)
Before I get into a discussion of some of the features available to Windows Phone 7 developers to send push notifications I want to talk a little bit about the push notification system available to iOS developers. When you want your application to support push notifications, you need to enable this capability using the iOS developer portal. From there, you must create different signing certificates – one for the development sandbox push notifications and one for production/live push notifications.
This means, you guessed it, that every message you send to Apple for processing by APNS needs to be signed by this certificate. Rather than sending an individual XML payload every time you have a push notification ready for delivery, you must open a stream-type connection to the APNS server and send a batch of pending notifications in a proprietary binary format. Further, it is then your responsibility to regularly poll the APNS infrastructure to get a list of delivery failures so that you can remove the tokens that failed to receive notifications from your delivery list.
Finally, and this has always been my least favorite feature of APNS, push notifications cannot be delivered to the simulator. The only way you can test push notifications is with a physical iOS device – iPhone, iPod Touch, or iPad. What this boils down to is a pretty hefty up-front investment before your application can support push notifications, including you standing up some server on the net that is capable of producing Apple’s proprietary binary message format as well as signing notification batches with either the development or production certificates (and yes, it’s up to you to make sure that your server knows which certificate to use).
In short, APNS is an incredibly powerful but difficult to master and effectively utilize technology. iOS developers who have spent any amount of time struggling with APNS development will file Windows Phone 7 push notifications a breath of fresh air.
Using Application Tile Schedules
One major difference between the way applications appear on Windows Phone 7 devices and how they appear on iOS devices is that WP7 applications can optionally have an application tile. This application tile is like a larger version of the application icon, but this tile is active. You’ve seen this where tiles for the various mail applications all show the unread message count and tiles for weather application show imagery that corresponds to the current weather in your area. The Xbox Live tile even shows your current Xbox Live avatar. Unfortunately, until the “Mango” release of Windows Phone 7 later this year, WP7 developers are limited to just using “scheduled-based” tiles rather than the fully interactive, animated tiles that come with some first-party WP7 applications (like Xbox Live).
Once created, application tiles can be updated on what is called a shell schedule. This schedule allows your application tile to refresh itself from a web URL or from an image stored in your application’s resources on some pre-determined interval. Your application can programmatically change this interval while running. One very important thing to remember about this schedule is that it is merely a suggestion and the actual execution time of the tile refresh is completely determined by the WP7 operating system. This means that if you want your weather tile to update every hour, you should not expect it to update every 60 minutes on the dot. Rather, your application will try and update itself every 60 minutes but if the WP7 device is busy, running low on memory, or otherwise occupied, your shell update schedule will be delayed until the OS has the spare resources to update your application tile.
In the current version of the SDK, all you can do is programmatically define your shell schedule and then call the Start method on that schedule as shown below:
1: ShellTileSchedule schedule = new ShellTileSchedule();
2:
3: schedule.Recurrence = UpdateRecurrence.Onetime;
4: schedule.StartTime = DateTime.Now.AddMinutes(1);
5: schedule.RemoteImageUri = new Uri(@"http://localhost/good.png");
6: schedule.Start();
In the preceding code, the shell schedule will try and set it’s image to a file called good.png roughly one minute from the time of execution. After that, the image will never again change (unless the shell schedule changes again). On my simulator, I have seen this simple one minute shell schedule take up to 5 minutes to take effect.
In addition to a one time change you can have the change occur repeatedly by supplying the UpdateRecurrence.Interval option. Since these tile schedules are not interactive and they cannot activate any of your application code, one trick often used by applications such as weather applications is to make the RemoteImageUri actually contain the necessary parameters for a dynamic image.
For example, you might see a weather application set the image to one that contains the zip code of an image that is dynamically generated by the server:
http://my.weatherapp.com/Wp7Tile/90210.png
By embedding useful parameters into the URI of the shell tile schedule, we can get as close as possible to truly dynamic tiles (like the Microsoft first-party tiles). This will have to tide us over until we get more powerful shell application tiles with the WP7 “Mango” release announced at the MIX 2011 conference.
Using Push Notifications
Push notifications allow your application to receive alerts about important events and pass that information along to your users. These notifications can be used for anything from reminding you about a low bank balance, a low stock price, or someone attacking your headquarters in an online game.
Using Push Notifications in your application basically boils down to a few activities, some of which need to be done by an application running somewhere on the internet that is reachable by your WP7 client app:
- Obtain a notification URL from the Microsoft Push Notification API. This is the URL to which HTTP messages will be POSTed by your application’s back-end infrastructure.
- Maintain a list of active notification URIs within your internet-hosted service.
- Use the notification URLs to send notifications to individual devices (tile, toast, or raw)
Throughout the rest of this article, I’ll show you how to do these things.
Obtain a Notification URL from Microsoft
The first thing that your application should do after initializing it’s main interface is obtain a reference to an instance of the NotificationChannel class. This is the channel through which push notifications occur. You’ll use this instance to add event handlers that respond to various push notifications and the channel URI given to you through this channel is the one that you will supply to your own custom notification service on the Internet in order to send notifications.
Creating your NotificationChannel instance looks something like this:
1: private void InitializeNotifications()
2: {
3: NotificationChannel = HttpNotificationChannel.Find(NOTIFY_CHANNEL_NAME);
4:
5: if (NotificationChannel == null)
6: {
7: NotificationChannel = new HttpNotificationChannel(NOTIFY_CHANNEL_NAME, NOTIFY_SERVICE_NAME);
8: SetupDelegates();
9:
10: NotificationChannel.Open();
11: }
12: else
13: {
14: SetupDelegates();
15:
16: Debug.WriteLine("Found existing channel at {0}", NotificationChannel.ChannelUri.ToString());
17: }
18:
19:
20: SubscribeToNotifications();
21: if (NotificationChannel.ChannelUri != null)
22: {
23: RegisterNotificationChannelWithService(NotificationChannel.ChannelUri.ToString());
24: }
25: }
In the preceding code NOTIFY_CHANNEL_NAME and NOTIFY_SERVICE_NAME are just constants. The channel name needs to be unique to your application and the service name needs to be unique to the service (remember that code you need to host on the web somewhere?) from which push notifications will originate. For my application (an app that tracks the progress of the zombie apocalypse), my constants were “us_vs_them_channel” and “www.usvsthemsampleapp.com” respectively. Also note that this code is inside my App.xaml.cs file and the NotificationChannel property is a public property of type HttpNotificationChannel.
The RegisterNotificationChannelWithService method is a method I wrote that talks to my cloud service that adds the notification URI to the list of running WP7 applications. You could host this code in Azure or on Amazon EC2 or anywhere that you can host services of any kind (you don’t need to use .NET as your back-end for this).
The SubscribeToNotifications method (also one that I wrote) selectively turns on support for receiving tile and toast notifications. Raw notifications are only possible when your application is running so you don’t need to do anything specific to enable that support:
1: private void SubscribeToNotifications()
2: {
3: // Cannot receive toasts without binding our notification channel
4: // to toasts. If we receive a toast and we have not bound our channel
5: // to those toasts, a very hard to debug exception will occur.
6: if (NotificationChannel.IsShellToastBound == true)
7: {
8: Debug.WriteLine("Already bound to Toast notification");
9: }
10: else
11: {
12: Debug.WriteLine("Registering to Toast Notifications");
13: NotificationChannel.BindToShellToast();
14: }
15:
16: if (NotificationChannel.IsShellTileBound == true)
17: {
18: Debug.WriteLine("Already bound to tile");
19: }
20: else
21: {
22: Debug.WriteLine("Registering to Tile Notification.");
23: NotificationChannel.BindToShellTile();
24: }
25: }
Finally I have the SetupDelegates method which attaches event handlers to all of the important events that might occur for push notifications:
1: private void SetupDelegates()
2: {
3: NotificationChannel.ChannelUriUpdated += new EventHandler<NotificationChannelUriEventArgs>(NotificationChannel_ChannelUriUpdated);
4: NotificationChannel.HttpNotificationReceived += new EventHandler<HttpNotificationEventArgs>(NotificationChannel_HttpNotificationReceived);
5: NotificationChannel.ShellToastNotificationReceived += new EventHandler<NotificationEventArgs>(NotificationChannel_ShellToastNotificationReceived);
6: NotificationChannel.ErrorOccurred += new EventHandler<NotificationChannelErrorEventArgs>(NotificationChannel_ErrorOccurred);
7: }
Sending Tile Notifications
In addition to periodic, scheduled updates to the shell tile we can send push notifications from the internet that contain changes to a tile such as the URI of a new image as well as a count which can be displayed in the corner of the tile image very much like the “badge count” that often appears in many different kinds of iOS applications (the most notable of which would be the Mail application).
In general you are probably going to send tile notifications from whatever service application you have running on the Internet, but it doesn’t always have to work that way. Unlike Apple’s APNS, there are no certificate signing requirements. If you have the notification channel URI given to an instance of a WP7 application at startup, then you can send a push notification to that application.
Here’s a method that sends a Tile notification to all of the channel URIs that have been stored by a notification service I wrote:
1: private void SendTile(string[] uriTargets, string backgroundImage, int count, string title)
2: {
3: Console.WriteLine("About to send tile to the following URIs:");
4:
5: foreach (string uri in uriTargets)
6: {
7: Console.WriteLine(uri);
8: HttpWebRequest sendNotificationRequest = (HttpWebRequest)WebRequest.Create(uri);
9:
10: // HTTP POST is the only allowed method to send the notification.
11: sendNotificationRequest.Method = "POST";
12: sendNotificationRequest.ContentType = "text/xml";
13: sendNotificationRequest.Headers.Add("X-WindowsPhone-Target", "token");
14: sendNotificationRequest.Headers.Add("X-NotificationClass", "1");
15:
16: string payloadString = string.Format("<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
17: "<wp:Notification xmlns:wp=\"WPNotification\">" +
18: "<wp:Tile>" +
19: "<wp:BackgroundImage>{0}</wp:BackgroundImage>" +
20: "<wp:Count>{1}</wp:Count>" +
21: "<wp:Title>{2}</wp:Title>" +
22: "</wp:Tile>" +
23: "</wp:Notification>", backgroundImage, count.ToString(), title);
24:
25: byte[] notificationMessage = System.Text.UTF8Encoding.UTF8.GetBytes(payloadString);
26: // Sets the web request content length.
27: sendNotificationRequest.ContentLength = notificationMessage.Length;
28:
29: // Sets the notification payload to send.
30: using (Stream requestStream = sendNotificationRequest.GetRequestStream())
31: {
32: requestStream.Write(notificationMessage, 0, notificationMessage.Length);
33: }
34:
35: // Sends the notification and gets the response.
36: HttpWebResponse response = (HttpWebResponse)sendNotificationRequest.GetResponse();
37: string notificationStatus = response.Headers["X-NotificationStatus"];
38: string notificationChannelStatus = response.Headers["X-SubscriptionStatus"];
39: string deviceConnectionStatus = response.Headers["X-DeviceConnectionStatus"];
40: }
41: }
Where Apple’s system has a fixed URL to which you send a notification targeted at a specific device token, Microsoft’s is much more direct. Every WP7 application that sets up a notification channel is assigned a unique URI. Sending POST messages to that URI will route requests through Microsoft which are then delivered directly to the device.
Your application never actually responds to a tile notification. The changes to the application tile are done by WP7 itself. Whether your application is running or not, the tile will change based on the information in the pushed tile notification.
Sending Toast Notifications
Toast notifications create little pop-up messages that appear at the top of your WP7 device when your application is not running and allow your application to display those toasts with a custom UI when they are delivered while the app is running. To send a toast notification to all of the registered channel URIs within a service, you can use a method that looks like this:
1: private void SendToast(string[] uriTargets, string text1, string text2)
2: {
3: Console.WriteLine("About to send to the following URIs:");
4:
5: foreach (string uri in uriTargets)
6: {
7: string subscriptionUri = uri;
8: Console.WriteLine(uri);
9: HttpWebRequest sendNotificationRequest = (HttpWebRequest)WebRequest.Create(subscriptionUri);
10:
11: // HTTP POST is the only allowed method to send the notification.
12: sendNotificationRequest.Method = "POST";
13: sendNotificationRequest.ContentType = "text/xml";
14: sendNotificationRequest.Headers.Add("X-WindowsPhone-Target", "toast");
15: sendNotificationRequest.Headers.Add("X-NotificationClass", "2");
16:
17: string payloadString = string.Format("<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
18: "<wp:Notification xmlns:wp=\"WPNotification\">" +
19: "<wp:Toast>" +
20: "<wp:Text1>{0}</wp:Text1>" +
21: "<wp:Text2>{1}</wp:Text2>" +
22: "</wp:Toast>" +
23: "</wp:Notification>", text1, text2);
24:
25: byte[] notificationMessage = System.Text.UTF8Encoding.UTF8.GetBytes(payloadString);
26: // Sets the web request content length.
27: sendNotificationRequest.ContentLength = notificationMessage.Length;
28:
29: // Sets the notification payload to send.
30: using (Stream requestStream = sendNotificationRequest.GetRequestStream())
31: {
32: requestStream.Write(notificationMessage, 0, notificationMessage.Length);
33: }
34:
35: // Sends the notification and gets the response.
36: HttpWebResponse response = (HttpWebResponse)sendNotificationRequest.GetResponse();
37: string notificationStatus = response.Headers["X-NotificationStatus"];
38: string notificationChannelStatus = response.Headers["X-SubscriptionStatus"];
39: string deviceConnectionStatus = response.Headers["X-DeviceConnectionStatus"];
40: }
41: }
And to respond to a toast notification from within your running application, the code is as simple as this:
1: void NotificationChannel_ShellToastNotificationReceived(object sender, NotificationEventArgs e)
2: {
3: string title = e.Collection.Values.First();
4: string message = e.Collection.Values.Skip(1).FirstOrDefault() ?? string.Empty;
5: Deployment.Current.Dispatcher.BeginInvoke(() => MessageBox.Show(message, title, MessageBoxButton.OK));
6: }
When your application receives a toast notification and it isn’t running, it might look something like this:
And if your application is running and it executes the preceding event handler code, you might see something like this:
Sending Raw Notifications
Sending Raw notifications allows your application to send an arbitrary payload (usually some kind of serialized object graph) to a running application. Raw notifications are only delivered when your application is running and are simply ignored/discarded when your application is not running. Given this fact, you should make sure that your application is designed in such a way that it does not rely on raw notifications for information that cannot otherwise be obtained between application executions.
To send a raw notification using a POCO (Plain-Old C# Object), one of the more popular methods is to create a C# object that has been decorated with the WCF DataContract and DataMember attributes, like this one:
1: using System;
2: using System.Net;
3: using System.Runtime.Serialization;
4:
5: namespace SharedData
6: {
7: [DataContract(Namespace="http://www.kotancode.com/zombies", Name="ZombiePopulationPayload")]
8: public class ZombiePopulationPayload
9: {
10: [DataMember]
11: public decimal Zombies { get; set; }
12:
13: [DataMember]
14: public decimal Humans { get; set; }
15: }
16: }
This class contains population numbers for humans and zombies so that your application can be pushed notifications, informing your users about the status of the current post-zombie-apocalypse humans vs. zombies war. To send an instance of this population status update, you might use a method that looks like this:
1: private void sendRawButton_Click(object sender, RoutedEventArgs e)
2: {
3: ZombiePopulationPayload zombiePopulation = new ZombiePopulationPayload() { Humans = 5.25m, Zombies = 8.23m };
4: DataContractSerializer ser = new DataContractSerializer(typeof(ZombiePopulationPayload));
5: MemoryStream ms = new MemoryStream();
6: ser.WriteObject(ms, zombiePopulation);
7: ms.Seek(0, SeekOrigin.Begin);
8: byte[] zombieBytes = ms.GetBuffer();
9:
10: NotificationService.NotificationsClient client = new NotificationService.NotificationsClient();
11:
12: string[] uris = client.GetRegisteredChannels();
13:
14:
15: Console.WriteLine("About to send raw data to the following URIs:");
16:
17: foreach (string uri in uris)
18: {
19:
20:
21: HttpWebRequest sendNotificationRequest = (HttpWebRequest)WebRequest.Create(uri);
22:
23: // HTTP POST is the only allowed method to send the notification.
24: sendNotificationRequest.Method = "POST";
25: sendNotificationRequest.Headers.Add("X-NotificationClass", "3");
26:
27: // Sets the web request content length.
28: sendNotificationRequest.ContentLength = zombieBytes.Length;
29:
30: // Sets the notification payload to send.
31: using (Stream requestStream = sendNotificationRequest.GetRequestStream())
32: {
33: requestStream.Write(zombieBytes, 0, zombieBytes.Length);
34: }
35:
36: // Sends the notification and gets the response.
37: HttpWebResponse response = (HttpWebResponse)sendNotificationRequest.GetResponse();
38: string notificationStatus = response.Headers["X-NotificationStatus"];
39: string notificationChannelStatus = response.Headers["X-SubscriptionStatus"];
40: string deviceConnectionStatus = response.Headers["X-DeviceConnectionStatus"];
41: }
42: }
In the preceding sample, the NotificationService.NotificationsClient is the WCF-generated client proxy to my Internet-hosted service that maintains the list of active push notification receivers (running WP7 applications). It uses a memory stream to serialize the object graph of the ZombiePopulationPayload object into an array of bytes that can be posted with the raw notification.
Now, to handle the reception of a raw notification, we need to deserialize the bytes we get from the push notification and convert them back into an instance of ZombiePopulationPayload. Once we have that, our application can handle this information however it likes (in my case, displaying the current status of the war against the zombie scourge):
1: void NotificationChannel_HttpNotificationReceived(object sender, HttpNotificationEventArgs e)
2: {
3: if (e.Notification.Body != null && e.Notification.Headers != null)
4: {
5: // Thanks to Daniel Vaughn (WP7 Unleashed by SAMS Press)
6: // for this tidbit on having to remove nulls from the serialized
7: // data contract sent from the push app/service
8: using (BinaryReader reader = new BinaryReader(e.Notification.Body))
9: {
10: byte[] bodyBytes = reader.ReadBytes((int)e.Notification.Body.Length);
11:
12: int lengthWithoutNulls = bodyBytes.Length;
13: for (int i = bodyBytes.Length - 1; i >= 0 && bodyBytes[i] == '\0';
14: i--, lengthWithoutNulls--)
15: {
16: /* Intentionally left blank. */
17: }
18:
19: byte[] cleanedBytes = new byte[lengthWithoutNulls];
20: Array.Copy(bodyBytes, cleanedBytes, lengthWithoutNulls);
21:
22: DataContractSerializer ser = new DataContractSerializer(typeof(ZombiePopulationPayload));
23: using (MemoryStream stream = new MemoryStream(cleanedBytes))
24: {
25: ZombiePopulationPayload zombiePopulation = (ZombiePopulationPayload)ser.ReadObject(stream);
26: Deployment.Current.Dispatcher.BeginInvoke(
27: () =>
28: MessageBox.Show(string.Format("Us:{0} Them:{1}",
29: zombiePopulation.Humans, zombiePopulation.Zombies),
30: "Population Update", MessageBoxButton.OK)
31: );
32: }
33: }
34: }
35: }
In my book, WP7 for iPhone Developers, I have the full downloadable code for this “Us vs. Them” zombie apocalypse application, including the server-side WCF implementation of the notification channel registry. With few exceptions, all of the major bits of code required to get that sample running are included in this article post. Again, many thanks to Daniel Vaughn for providing a blog post that showed that we need to strip unnecessary nulls out of the push notification before we try and reconstitute the object graph with the data contract serializer.
When we get a raw notification, our message box looks something like this:
Summary
This article had a lot of code but one of the things I wanted to make clear with this article is just how easy it is to work with push notifications in WP7. Not only can you receive push notifications directly from within the simulator, but you don’t need to worry about certificate signing or anything else – Microsoft takes care of the security of the messages behind the scenes, freeing you from that particular (and annoying) requirement.
Push notifications are so easy to use that, if your application needs to send information directly to application users immediately when important events occur, you should definitely look into using them in your application. The barrier to entry, learning curve, and infrastructure required for WP7 applications is much smaller than what is required for iOS push notifications so if you’re planning on porting your application from iOS to WP7, push notifications should not pose a major problem for you.
About the Author
Kevin Hoffman (http://www.kotancode.com/) is a Systems Architect for Oakleaf Waste Management (http://www.oakleafwaste.com/), freelance developer, and author of multiple books including the upcoming WP7 for iPhone Developers and co-author of books such as ASP.NET 4 Unleashed and SharePoint 2007 Development Unleashed. He is the author of the Kotan Code blog and has presented at Apple's WWDC twice and guest lectured at Columbia University on iPhone development.