Implementing Push Notifications for iOS with C# & MonoTouch using the Cloud & Urban Airship

A bit of mobile goodness today.

Communication-wise push notifications are one of my personal favorite features on any mobile device platform Smile So, it seems natural to write a bit about it.
This time we will see how to use a third-party (but still free) service in the cloud to handle the push notifications for us (independent of Apple, Google, Blackberry or Microsoft). The sample shown here in this blog entry is for iOS and is written and built in C# with MonoTouch.

Although, the ideas of push notifications are similar between iOS; Windows Phone 7 and other device platforms, there are differences in implementation (and partly in architecture). What I wanted to achieve for my push notification-enabled apps was two-folded:

  1. Being able to easily scale out and handle potentially huge numbers of user/subscribers and push messages without having to write, host and maintain my own code and services and servers
  2. Not having to implement push notifications logic (like register, unregistering, receiving, error handling etc) for each and every platform over and over

This lead me to two solutions:

  1. Use C# everywhere (oops, is this trademarked anywhere? Winking smile) with MonoTouch, MonoDroid and WP7 developer tools
  2. Use a cloud service offered by an independent third party. In my case this is Urban Airship (free for 1,000,000 messages per month)

Rock da cloud.

Turns out that Urban Airship has a web-friendly REST API (oh wonder!) which accepts JSON messages. Honestly, it was not hard to decide to write some C# to utilize this service instead of having to deal with Apple’s APNs directly.

Note: the code illustrated here is a bit outdated. It relies on some iOS intrinsics like UIApplication and NSUserDefaults and therefore is not portable across devices. I have some platform-independent code lurking here but need to fully implement, test and polish it first.

The most important piece of code is the PushNotifications class which handles all the necessary communication, especially with the Urban Airship service:

public static class PushNotifications
{
     public static void Subscribe ()
     {
          UIApplication.SharedApplication.RegisterForRemoteNotificationTypes (
UIRemoteNotificationType.Alert | UIRemoteNotificationType.Badge); } public static void Unsubscribe () { UIApplication.SharedApplication.UnregisterForRemoteNotifications (); } public static void EnsureDeviceRegistration (NSData deviceToken) { var str = (NSString)Runtime.GetNSObject ( Messaging.intptr_objc_msgSend (deviceToken.Handle, new Selector(
"description").Handle)); var deviceTokenString = str.ToString ().Replace ("<", "").Replace (">", "")
.Replace (" ", ""); var token = LoadFromSettings (); if (String.IsNullOrEmpty (token) || token != deviceTokenString) { Console.WriteLine (String.Format ("EnsureDeviceRegistration: {0}, {1}",
deviceTokenString, deviceTokenString.Length)); var server = @"https://go.urbanairship.com"; var url = String.Format("{0}{1}{2}", server, "/api/device_tokens/",
deviceTokenString); Console.WriteLine ("EnsureDeviceRegistration: URL: " + url); var putUrl = new Uri(url); var request = (HttpWebRequest)HttpWebRequest.Create(putUrl); request.Method = "PUT"; request.ContentType= "application/json"; request.Credentials = new NetworkCredential(Settings.APNsUser,
Settings.APNsPassword); var payload = @"{""alias"": """ + Settings.DeviceAlias + @"""}"; Console.WriteLine ("EnsureDeviceRegistration: Payload: " + payload); var byteArray = Encoding.UTF8.GetBytes(payload); request.ContentLength = byteArray.Length; var dataStream = request.GetRequestStream(); dataStream.Write(byteArray, 0, byteArray.Length); dataStream.Close(); var response = (HttpWebResponse)request.GetResponse(); Console.WriteLine("EnsureDeviceRegistration: Status: " +
response.StatusCode); SaveToSettings (deviceTokenString); } else { Console.WriteLine("EnsureDeviceRegistration: device already registered."); } } private static string LoadFromSettings () { var prefs = NSUserDefaults.StandardUserDefaults; return prefs.StringForKey("DeviceToken"); } private static void SaveToSettings (string deviceTokenString) { var prefs = NSUserDefaults.StandardUserDefaults; prefs["DeviceToken"] = new NSString (deviceTokenString); } }


Pretty simple – I did deliberately decide not to use any JSON API or serializer here.

Then, for my iOS apps I usually implement a couple of overrides in my AppDelegate class like this:

public override void RegisteredForRemoteNotifications (UIApplication application, 
NSData deviceToken) { Console.WriteLine ("RegisteredForRemoteNotifications"); PushNotifications.EnsureDeviceRegistration (deviceToken); } public override void ReceivedRemoteNotification (UIApplication application,
NSDictionary userInfo) { Console.WriteLine ("ReceivedRemoteNotification"); var aps = userInfo.ObjectForKey (new NSString (
CommunicationConstants.ApsRootElement)) as NSDictionary; var alert = aps.ObjectForKey (new NSString (
CommunicationConstants.ApsAlertElement)).ToString (); Console.WriteLine ("Alert: " + alert); var sound = SystemSound.FromFile (new NSUrl (ResourcesConstants.ExplodeSound)); sound.PlayAlertSound (); var av = new UIAlertView("Notification:", alert, null, "OK", null); av.Show (); (navigationController.VisibleViewController as EpisodesTableViewController)
.LoadEpisodesData (); }
public override void FailedToRegisterForRemoteNotifications (UIApplication application,
NSError error) { Console.WriteLine (String.Format ("FailedToRegisterForRemoteNotifications:
{0}, {1}, {2}"
, error.Domain, error.Code, error.LocalizedDescription)); }

Then in my FinishedLaunching method (or where you decide to do it) I subscribe to push notifications:
PushNotifications.Subscribe();


That’s it – hope this helps someone. If you have more questions, please let me know.


P.S.: a big Kudos to the MonoTouch and MonoDroid teams and their immediate help with technical questions & issues!