Push notifications in iOS with MonoTouch

A recurring theme when building mobile apps is push notifications. I’m working on a couple of apps at Info Support using MonoTouch for iOS and we want to add push notifications to those apps. There’s a lot of interesting and very useful information on the internet about the implementation of notifications, which is actually pretty straight forward once you know the API.

First of all, Apple has an excellent iOS Developer Library with very comprehensive and pleasant to read information about the iOS API’s. Here is the section on Notifications.

Apple makes a distinction between local and remote notifications. Local notifications being notifications you schedule and “send” from the device itself from a background task, remote notifications being the push notifications coming from the Apple Push Notification service (APN). I’m specifically looking at push notifications.

Handling notifications on the iDevice

There is a great sample implementation showing the basics of handling notifications in the iOS app here on Google Code. I shamelessly took the code snippets from that sample 🙂 In short, it’s as simple as this (in C#/MonoTouch):

  
 // This method is invoked when the application has loaded its UI and its ready to run  
 public override bool FinishedLaunching (UIApplication app, NSDictionary options)  
 {  
 //This tells our app to go ahead and ask the user for permission to use Push Notifications  
 // You have to specify which types you want to ask permission for  
 // Most apps just ask for them all and if they don’t use one type, who cares  
 UIApplication.SharedApplication.RegisterForRemoteNotificationTypes(UIRemoteNotificationType.Alert  
 | UIRemoteNotificationType.Badge  
 | UIRemoteNotificationType.Sound);

 //The NSDictionary options variable would contain our notification data if the user clicked the ‘view’ button on the notification  
 // to launch the application. So you could process it here. I find it nice to have one method to process these options from the  
 // FinishedLaunching, as well as the ReceivedRemoteNotification methods.  
 processNotification(options, true);

 //See if the custom key value variable was set by our notification processing method  
 if (!string.IsNullOrEmpty(launchWithCustomKeyValue))  
 {  
 //Bypass the normal view that shows when launched and go right to something else since the user  
 // launched with some custom value (eg: from a remote notification’s ‘View’ button being pressed, or from a url handler)

 //TODO: Insert your own logic here  
 }

 // If you have defined a view, add it here:  
 // window.AddSubview (navigationController.View);

 window.MakeKeyAndVisible ();

 return true;  
 }  

On startup, the AppDelegate calls UIApplication.SharedApplication.RegisterForRemoteNotificationTypes() from within the FinishedLaunching method, in which you specify which notifications you are interested in. These can be a combination of UIRemoteNotificationType.Alert (for alert texts), UIRemoteNotificationType.Badge (for numbers to update the application’s badge with) and UIRemoteNotificationType.Sound (for a custom sound to be played upon receipt of the notification).

iOS will register itself with the APN and will do a callback to the application when that is finished. This can have one of two outcomes: either it succeeds or it fails. You handle this by overriding two methods in the AppDelegate: RegisteredForRemoteNotifications and FailedToRegisterForRemoteNotifications. In case of success, Apple will give you a device token with which the APN can address your application on your device. Typically, what you do in RegisteredForRemoteNotifications is passing that device token to your application services (the server application that generates the notifications) so that it can find you.


 public override void RegisteredForRemoteNotifications (UIApplication application, NSData deviceToken)  
 {  
 //The deviceToken is of interest here, this is what your push notification server needs to send out a notification  
 // to the device. So, most times you’d want to send the device Token to your servers when it has changed

 //First, get the last device token we know of  
 string lastDeviceToken = NSUserDefaults.StandardUserDefaults.StringForKey("deviceToken");

 //There’s probably a better way to do this  
 NSString strFormat = new NSString("%@");  
 NSString newDeviceToken = new NSString(MonoTouch.ObjCRuntime.Messaging.IntPtr_objc_msgSend_IntPtr_IntPtr(new MonoTouch.ObjCRuntime.Class("NSString").Handle, new MonoTouch.ObjCRuntime.Selector("stringWithFormat:").Handle, strFormat.Handle, deviceToken.Handle));

 //We only want to send the device token to the server if it hasn’t changed since last time  
 // no need to incur extra bandwidth by sending the device token every time  
 if (!newDeviceToken.Equals(lastDeviceToken))  
 {  
 //TODO: Insert your own code to send the new device token to your application server  
 //Save the new device token for next application launch  
 NSUserDefaults.StandardUserDefaults.SetString(newDeviceToken, "deviceToken");  
 }  
 }

public override void FailedToRegisterForRemoteNotifications (UIApplication application, NSError error)  
 {  
 //Registering for remote notifications failed for some reason  
 //This is usually due to your provisioning profiles not being properly setup in your project options  
 // or not having the right mobileprovision included on your device  
 // or you may not have setup your app’s product id to match the mobileprovision you made  
 Console.WriteLine("Failed to Register for Remote Notifications: {0}", error.LocalizedDescription);  
 }  

Now, in the first snippet (FinishedLaunching()), you might also have noticed the call to processNotification(), which handles an incoming notification. We’ll get to the implementation of that method further on, but what’s important here is that there are two scenario’s to take care of: the first is receiving a notification while the app is running. This is handled by overriding the ReceivedRemoteNotification method in your AppDelegate. When a notification comes in, iOS will call this method when the app is active. The other scenario is when the app is not running. When iOS receives a notification and the user chooses to take action upon it, the app will launch and iOS will pass the notification data to FinishedLaunching. This is why you’ll also want to handle that from FinishedLaunching().

When a notification comes in, you basically get an NSDictionary object containing the notification data. Basically this is some JSON encoded data containing the alert, badge and name of the sound file to be played (if applicable). An alert can be a simple text string, but might also be a more complex structure, if it is a localized message. So, you process notifications from both FinishedLaunching() and ReceivedRemoteNotification(). A good practice would be to implement the handling in a separate method to which you delegate the work from both locations. So here is the implementation of processToken():


 public override void ReceivedRemoteNotification (UIApplication application, NSDictionary userInfo)  
 {  
 //This method gets called whenever the app is already running and receives a push notification  
 // YOU MUST HANDLE the notifications in this case. Apple assumes if the app is running, it takes care of everything  
 // this includes setting the badge, playing a sound, etc.  
 processNotification(userInfo, false);  
 }

void processNotification(NSDictionary options, bool fromFinishedLaunching)  
 {  
 //Check to see if the dictionary has the aps key. This is the notification payload you would have sent  
 if (null != options && options.ContainsKey(new NSString("aps")))  
 {  
 //Get the aps dictionary  
 NSDictionary aps = options.ObjectForKey(new NSString("aps")) as NSDictionary;

 string alert = string.Empty;  
 string sound = string.Empty;  
 int badge = -1;

 //Extract the alert text  
 //NOTE: If you’re using the simple alert by just specifying " aps:{alert:"alert msg here"} "  
 // this will work fine. But if you’re using a complex alert with Localization keys, etc., your "alert" object from the aps dictionary  
 // will be another NSDictionary… Basically the json gets dumped right into a NSDictionary, so keep that in mind  
 if (aps.ContainsKey(new NSString("alert")))  
 alert = (aps[new NSString("alert")] as NSString).ToString();

 //Extract the sound string  
 if (aps.ContainsKey(new NSString("sound")))  
 sound = (aps[new NSString("sound")] as NSString).ToString();

 //Extract the badge  
 if (aps.ContainsKey(new NSString("badge")))  
 {  
 string badgeStr = (aps[new NSString("badge")] as NSObject).ToString();  
 int.TryParse(badgeStr, out badge);  
 }

 //If this came from the ReceivedRemoteNotification while the app was running,  
 // we of course need to manually process things like the sound, badge, and alert.  
 if (!fromFinishedLaunching)  
 {  
 //Manually set the badge in case this came from a remote notification sent while the app was open  
 if (badge >= 0)  
 UIApplication.SharedApplication.ApplicationIconBadgeNumber = badge;  
 //Manually play the sound  
 if (!string.IsNullOrEmpty(sound))  
 {  
 //This assumes that in your json payload you sent the sound filename (like sound.caf)  
 // and that you’ve included it in your project directory as a Content Build type.  
 var soundObj = MonoTouch.AudioToolbox.SystemSound.FromFile(sound);  
 soundObj.PlaySystemSound();  
 }

 //Manually show an alert  
 if (!string.IsNullOrEmpty(alert))  
 {  
 UIAlertView avAlert = new UIAlertView("Notification", alert, null, "OK", null);  
 avAlert.Show();  
 }  
 }  
 }

 //You can also get the custom key/value pairs you may have sent in your aps (outside of the aps payload in the json)  
 // This could be something like the ID of a new message that a user has seen, so you’d find the ID here and then skip displaying  
 // the usual screen that shows up when the app is started, and go right to viewing the message, or something like that.  
 if (null != options && options.ContainsKey(new NSString("customKeyHere")))  
 {  
 launchWithCustomKeyValue = (options[new NSString("customKeyHere")] as NSString).ToString();

 //You could do something with your customData that was passed in here  
 }  
 }  

Pretty nifty!

Sending notifications

In short, sending notifications to a user means that you send a notification payload to the APN using the device token to address the user. There are several ways to do that.

Christian Weyer has an excellent blog post on the implementation of push notifications using a third party for the distribution of notifications: Urban Airship. Urban Airship has a free service with which you can send up to 1,000,000 messages a month. If you want more service and features, there’s an attractive pricing model. Urban Airship abstracts the handling of multiple platforms, such as Apple, Microsoft, BlackBerry and Google, so you can service almost any type of device. PushIO offers a similar service.

If you only have to send notifications to Apple devices, since – let’s face it – iOS is the only OS that really matters… there is a nice open source C# library that does the heavy lifting for you: APNS-Sharp. This library can also handle AppStore Feedback and receipts for In App Purchases for you. Nice!

Of course, Marcel, Willem and I are targeting Android and Windows Phone as well, so a service like Urban Airship or PushIO would be ideal.

UPDATE: Added the suggestions made by Slava. Thanks for that!

Share this: