.NET Zone is brought to you in partnership with:

Sasha Goldshtein is a Senior Consultant for Sela Group, an Israeli company specializing in training, consulting and outsourcing to local and international customers.Sasha's work is divided across these three primary disciplines. He consults for clients on architecture, development, debugging and performance issues; he actively develops code using the latest bits of technology from Microsoft; and he conducts training classes on a variety of topics, from Windows Internals to .NET Performance. You can read more about Sasha's work and his latest ventures at his blog: http://blogs.microsoft.co.il/blogs/sasha. Sasha writes from Herzliya, Israel. Sasha is a DZone MVB and is not an employee of DZone and has posted 204 posts at DZone. You can read more from them at their website. View Full User Profile

Windows Azure Mobile Services "Rent a Home" Sample, Part 4: Push Notifications

06.04.2013
| 2981 views |
  • submit to reddit

In the previous posts we explored data and authentication on the backend and client-side. This post explains how the Rent a Home application uses push notifications on all four platforms to let users know immediately when a new apartment listing is added.

First, it's important to understand the general model used by all mobile platforms for push notifications. The mobile application calls a set of local APIs to obtain a push token, referred to as channel URIregistration id, or device token on the various platforms. The mobile application then has to send that push token to the backend, which stores it for later use. When there is a push notification to deliver, the backend uses the push tokens in a platform-specific way to send a notification to registered devices. (By platform-specific way, I mean that the Apple Push Notification Service (APNS) has different semantics and of course a different endpoint than the Windows Notification Service (WNS). In fact, there's even a difference in payloads, endpoints, and tokens between the Windows Notification Service (WNS), used by Windows 8 applications, and the Mobile Push Notification Service (MPNS), used by Windows Phone applications!)

NOTE: You will notice throughout this post that most of the details on the client-side are not at all dependent on Windows Azure Mobile Services. This is not coincidental -- the push support provided by Windows Azure Mobile Services is primarily on the backend.

Android
Android push relies on Google Cloud Messaging (GCM), which consists of a client-side library (gcm.jar) and a server backend that delivers push notifications. GCM is supported on Android 2.2+, and requires a device that has Google Play installed. (These requirements cover more than 90% of the Android install base.)

Before you can use GCM in your app, you need to register online with Google and obtain a sender id for your backend. You can also restrict the set of IP addresses that are allowed to communicate with GCM's push infrastructure. At runtime, on a client's device, the Rent a Home application uses standard APIs from the GCM library, and requires some configuration in the application's manifest. None of this is specific to Windows Azure Mobile Services. (For a detailed walkthrough, see the Google Developer documentation.)

//This code goes in one of the initialization methods in the
//application's main activity
GCMRegistrar.checkDevice(this);
GCMRegistrar.checkManifest(this);
String registrationId = GCMRegistrar.getRegistrationId(this);
if ("".equals(registrationId)) {
  GCMRegistrar.register(this, GCM_SENDER_ID);
}

When the GCM infrastructure successfully generates a registration token for the application/device combination, it delivers an intent to a service within your application. You don't have to implement the service from scratch -- you simply derive from GCMBaseIntentService and override a few methods, includingonRegistered, which lets you know that the registration process has completed successfully.

public class GCMIntentService extends GCMBaseIntentService {
  @Override
  protected void onRegistered(Context context, String regId) {
    mobileService.getTable("channels", Channel.class).insert(
      new Channel(regId),
      new TableOperationCallback() {
        public void onCompleted(Channel item, Exception exception,
                                ServiceFilterResponse response) {
          if (exception != null) {
            displayError(exception);
          }
        }
      });
  }
}

At this point, it's safe to send the registration id to the backend and store it there. Our application uses a table called channels for this purpose, which is represented by a very simple object called Channel:

@SuppressWarnings("unused")
public class Channel {
  private int id;
  private String uri;
  private String type;
  
  public Channel(String registrationId) {
    this.uri = registrationId;
    this.type = "Android";
  }
}

When the backend pushes a notification to the client, there is no automatic processing of that notification to display a message to the user. It is the developer's responsibility to process the intent delivered to theonMessage method of the GCMIntentService class and act accordingly. This affords you the flexibility of processing push notifications entirely in code, without popping up any new UI. The Rent a Home application places a notification in the Android notification tray when a push notification is received, using the Notification.Builder API:

@Override
protected void onMessage(Context context, Intent intent) {
  Intent activityIntent = new Intent(this, HomeActivity.class);
  activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  PendingIntent pendingIntent = PendingIntent.getActivity(
      this, 0, activityIntent, PendingIntent.FLAG_UPDATE_CURRENT);
  Notification notification = new NotificationCompat.Builder(this)
      .setSmallIcon(R.drawable.appicon)
      .setContentTitle("New apartment added!")
      .setContentText(
          String.format("New %s-bedroom apartment added at %s",
          intent.getStringExtra("bedrooms"),
          intent.getStringExtra("address")))
      .setAutoCancel(true)
      .setContentIntent(pendingIntent)
      .build();
  NotificationManager manager = (NotificationManager)
                        getSystemService(NOTIFICATION_SERVICE);
  manager.notify(0, notification);
}

iOS
On iOS, registering for push notification support in your application involves a number of steps that developers often find daunting. However, it's just a matter of following instructions to the letter -- you have to generate a code signing request, provide it in the iTunes Connect Provisioning Portal, download the resulting certificate, export it in the .p12 format, and upload that .p12 file back to the portal. Very detailed instructions can be found at Matthijs Hollemans' two-part tutorial.

NOTE: You can use push notifications only if you have an Apple iOS Developer Account, and a physical iOS device. You cannot test push notifications in the iOS Simulator.

After completing the configuration steps, registering and processing push notifications on iOS is actually fairly easy. (Again, nothing here is specific to Windows Azure Mobile Services.) You begin by registering for push notifications when your application launches -- a typical place for this would be theUIApplicationDelegate's application:didFinishLaunchingWithOptions: method. You then provide a couple of methods on the application delegate that are invoked when the registration completes, and provide you the push token you have to send to your service's backend.

- (BOOL)application:(UIApplication *)application
        didFinishLaunchingWithOptions:(NSDictionary *)options
{
  [application registerForRemoteNotificationTypes:
    UIRemoteNotificationTypeAlert |
    UIRemoteNotificationTypeBadge |
    UIRemoteNotificationTypeSound
  ];    
  return YES;
}

- (void)application:(UIApplication *)application
  didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)token
{
  NSCharacterSet *ltgt = [NSCharacterSet
                          characterSetWithCharactersInString:@"<>"];
  NSString *strToken = [[deviceToken description]
                        stringByTrimmingCharactersInSet:ltgt];
  MSTable *channelsTable = [sharedClient getTable:@"channels"];
  NSDictionary *channel = @{ @"uri" : token, @"type" : @"iOS" };
  [channelsTable insert:channel
             completion:^(NSDictionary *item, NSError *error) {
    if (error) {
        NSLog(@"Error inserting device token: %@",
              [error localizedDescription]);
    }
  }];
}

- (void)application:(UIApplication *)application
  didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
  NSLog(@"Failed to register for remote notifications: %@",
        [error localizedDescription]);
}

Finally, there's the matter of processing push notifications. If the application is not currently running, or is backgrounded, a push notification will trigger a message in the iOS Notification Center. When the user taps the message, the application will be launched, and the launch options dictionary will contain the push notification's payload under the UIApplicationLaunchOptionsRemoteNotificationKey key. You can then choose to simply refresh your UI with up to date information, or display an additional message to the user pertaining to the push notification. The Rent a Home application takes the first approach.

If the application is running in the foreground when the push notification is received, iOS calls theUIApplicationDelegate's application:didReceiveRemoteNotification: method. This method takes an NSDictionaryprovided by the backend with additional information about the push notification. The Rent a Home application processes that information and displays an alert with the new apartment's details:

- (void)application:(UIApplication *)application
        didReceiveRemoteNotification:(NSDictionary *)userInfo
{
  NSString *message = [NSString stringWithFormat:
            @"New %@-bedroom apartment added at %@",
            userInfo[@"bedrooms"], userInfo[@"address"]];
  [[[UIAlertView alloc] initWithTitle:@"Notification"
                              message:message
                             delegate:nil
                    cancelButtonTitle:@"OK"
                    otherButtonTitles:nil] show];
}

Windows Phone 8
On Windows Phone, before you can use push notifications, you have to associate your application with Microsoft's Push Notification Service (MPNS), although this is only necessary if you intend to send more than 500 push notifications per user per day. Then, when a push notification is sent to your application, it can either update your application's live tile or display a toast. Regardless of the kind of notification you use, the application must be pinned to the Start screen to react to push notifications.

To obtain a push token on Windows Phone, the Rent a Home application uses the following simple API (again, this is not specific to Windows Azure Mobile Services):

private async Task AcquirePushChannel()
{
  CurrentChannel = HttpNotificationChannel.Find("ApartmentPush");
  if (CurrentChannel == null)
  {
    CurrentChannel = new HttpNotificationChannel("ApartmentPush");
    CurrentChannel.Open();
    CurrentChannel.BindToShellToast();
  }
  Channel channel = new Channel
  {
    Uri = CurrentChannel.ChannelUri.ToString(),
    Type = "Windows Phone 8"
  };
  await MobileService.GetTable<Channel>().InsertAsync(channel);
}

private async void Application_Launching(object sender,
                                         LaunchingEventArgs e)
{
    await AcquirePushChannel();
}

The Channel class in the preceding code snippet is very simple (and very similar to the Android one):

[DataTable(Name = "channels")]
public class Channel
{
  public int Id { get; set; }

  [DataMember(Name = "type")]
  public string Type { get; set; }

  [DataMember(Name = "uri")]
  public string Uri { get; set; }
}

If the app is running while the push notification is delivered, it can process that notification to refresh the UI. The main page registers for this event as follows:

App.CurrentChannel.ShellToastNotificationReceived += (s, e) =>
{
  Dispatcher.BeginInvoke(InitApartments);
};

Windows 8
On Windows 8, initializing the push infrastructure and delivering the channel URI to the backend is fairly similar. (One thing to watch for, however, is that Windows 8 offers a much bigger variety of push notification types, including dozens of toast templates, dozens of tile templates, and tile badges. For more details about the supported notification templates, see Jim O'Neil's excellent walkthrough on MSDN Blogs.)

public App()
{
  this.InitializeComponent();
  this.Suspending += OnSuspending;

  AcquirePushChannel().Wait();
}

private async Task AcquirePushChannel()
{
  CurrentChannel = await PushNotificationChannelManager.
          CreatePushNotificationChannelForApplicationAsync();
  var channelTable = MobileService.GetTable<Channel>();
  Channel channel = new Channel
  {
    Uri = CurrentChannel.Uri,
    Type = "Windows 8"
  };
  await channelTable.InsertAsync(channel);
}

Similarly to Windows Phone, if the application is running while the push notification is received, it refreshes the UI. This is done by subscribing to the following push notification channel's event:

public MainPage()
{
  //Omitted for brevity
  App.CurrentChannel.PushNotificationReceived +=
                    CurrentChannel_PushNotificationReceived;
}

async void CurrentChannel_PushNotificationReceived(
  PushNotificationChannel sender,
  PushNotificationReceivedEventArgs args)
{
  await Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
                            InitApartments);
}

Server-Side Support
On the Mobile Service backend, we store push tokens in the channels table. It would be a good idea to prune this table once in a while, and definitely to block duplicate registrations. The pruning is left as an exercise for the reader, and the deduplication is simply part of the table's insert script:

function insert(item, user, request) {
  var channelTable = tables.getTable('channels');
  channelTable
    .where({ uri: item.uri, type: item.type })
    .read({ success: insertChannelIfNotFound });

  function insertChannelIfNotFound(existingChannels) {
    if (existingChannels.length > 0) {
      request.respond(200, existingChannels[0]);
    } else {
      request.execute();
    }
  }
}

Next, to send a push notification whenever an apartment listing is added, we need to make changes to the insert script on the apartment table. Specifically, we have to iterate the list of channels, and, depending on the type of the channel, use a platform-specific API to issue the push notification. Fortunately, Windows Azure Mobile Services provides a push object that encapsulates the HTTP requests and responses involved with push messages, but it is still up to us to provide the specific payload for each platform:

function insert (item, user, request) {
  //Most of the code omitted for brevity
  var channelTable = tables.getTable('channels');
  channelTable.read({
    success: function(channels) {
      channels.forEach(function(channel) {
        if (channel.type === "Windows 8") {
          push.wns.sendToastText04(channel.uri, {
            text1: 'New apartment added',
            text2: item.address,
            text3: item.bedrooms + ' bedrooms'
          });
        }
        if (channel.type === "Windows Phone 8") {
          push.mpns.sendToast(channel.uri,
            'New apartment added',
            'At ' + item.address + ' with ' +
                    item.bedrooms + ' bedrooms');
        }
        if (channel.type === "iOS") {
          push.apns.send(channel.uri, {
            alert: 'New ' + item.bedrooms +
                   '-bedrooms apartment added at ' + item.address,
            payload: {
              address: item.address,
              bedrooms: item.bedrooms
            }
          });
        }
        if (channel.type === "Android") {
          push.gcm.send(channel.uri,
            {
              bedrooms: item.bedrooms.toString(),
              address: item.address
            }
          );
        }
      }); //end of channels.forEach
    }     //end of success callback function
  });     //end of channelTable.read
}

NOTE: In a production application, pushing an update to all registered users whenever an apartment listing is added would be counter-effective. Users aren't interested in every single apartment listing in the world. Some sort of segmentation or subscription model would be necessary in the backend to send push notifications only to users that are genuinely interested in that apartment listing. Today, you have to do this manually, but it is very likely to be supported in the near future with Windows Azure Service Bus Notification Hubs, which is a super-scalable infrastructure for delivering push notifications with loosely coupled subscription topics.

Summary
This post concludes our exploration of the Rent a Home application on all four platforms, and the backend support that made it possible. We've seen how to access data and protect that data from unauthorized users, how to authenticate users and enrich their information with server scripts, and how to send push notifications to users' devices. Armed with these skills, you can now use Windows Azure Mobile Services to develop your own application on every platform, using a single unified backend. You are more than welcome to use parts of the Rent a Home application itself -- it is available on GitHub.

I am posting short links and updates on Twitter as well as on this blog. You can follow me: @goldshtn

Published at DZone with permission of Sasha Goldshtein, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)