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!

Advertisements

19 thoughts on “Push notifications in iOS with MonoTouch”

    1. Thanks for the tip. No specific reason for Urban. Just to point out that there are services available ou there. If Appoxee is free then that would be a great option as well. Depends on the service level you need.

  1. Hi mate

    Thanks a lot for the article – push notification implementation was (almost) painless.

    I had a couple of issues with processNotification code – app crashed.
    My changes:

    1. I added a null check for options – line 12
    2. Used NSObject instead of NSString for badge number in line 35

    Cheers

    1. Thanks for the suggestions, I’ll put them in. If it makes the code more robust, all for the better 🙂

      I should make a blogpost on the server side (using C#) as well…

      1. The article about server-side would be great!
        I am stuck with APNS-Sharp – it does not even compile.
        😉

        Though I am more interested in in-app purchase, as push notification seems to work with Urban Airship.

  2. Great post! Is there a MonoTouch delegate to override in order to handle the scenario when a user disables Notifications in the Settings app of the phone? I’d like to handle this scenario and then send an API request to UrbanAirship to mark the device token as inactive.

  3. Hi Roy,

    In the RegisteredForRemoteNotifications method (from line 10 – 11) shouldn’t this be like

    var strFormat = new NSString(“%@”);
    var dt = 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));
    var newDeviceToken = dt.ToString().Replace(“”, “”).Replace(” “, “”);

    Or does your server-side code that makes use of APNS-Sharp take care of these replacements? This is confusing to me because APNS-Sharp is deprecated, and replaced by PushSharp (which I’m using right now). If I do it like this in my app (and I use PushSharp), it’s not working (found that out the hard way 🙂 )

    Shalom

  4. add a null test for this line

    if (null != options && options.ContainsKey(new NSString(“customKeyHere”)))

  5. The remote notification is only in the “aps” key in the options dictionary root when the app is running (foreground or background) when the remote notification is received. If the app isn’t running, the options dictionary passed to FinishedLaunching has the “aps” key in another key called “UIApplicationLaunchOptionsRemoteNotificationKey” in the options dictionary. I don’t see this reflected in your code.

    https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/Reference/Reference.html#//apple_ref/c/data/UIApplicationLaunchOptionsRemoteNotificationKey

  6. Hi this article was nice, but I have small doubt,
    By default after receiving push notifications, it was showing the sent message on notifications tab.

    But I want to display custom message like “You have new notification” on notifications tab, is it possible?

    1. Hi,

      Thanks for the reply! I think that would be difficult, since the message format is handled by iOS itself and automatically displayed. In iOS 7, you can now also send silent notifications, or trigger a background task to refresh data and provide a generic message like you suggest. You can read about that on the Xamarin docs site. Does that help?

      Cheers!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s