Share via


April 2014

Volume 29 Number 4


Windows Phone : Build a Cross-Platform, Mobile Golf App Using C# and Xamarin

Wallace B. McClure | April 2014 | Get the Code

One of the fun things about the return of golf season is participating in tournaments that feature events such as a longest drive contest. In these, a person’s first shot off a designated hole is measured against others in the tournament. The longest drive during the day is declared the winner. However, these contests typically don’t have centralized scoring. If you’re in the first group, you don’t know until after the event is over where your shots stand in relation to everyone else’s. Why not use a mobile phone to record the starting point and ending point of drives and store the information in a cloud-hosted database?

The options for building such an app are many, which can be confusing. In this article, I’ll walk through how I built such an app using the back-end options in Windows Azure and how I handled various issues. I’ll show the code for writing an app for Windows Phone as well as iOS using Xamarin.

Several features were required. The app needed to run across mobile devices and multiple device OSes. It had to be a native app that looked just like all of the others on a device. The back-end server had to be always available, with minimal hassle to the developer (me). The cloud services had to provide as much help as possible in the area of cross-platform development. The back-end database needed to provide some amount of geolocation functionality.

Why C#?

Options to build cross-platform apps include: mobile Web apps; Xamarin C# for the iPhone (and Android); forgoing cross-platform and building apps in the vendor-directed language (the Microsoft .NET Framework for Windows Phone and Objective-C for the iPhone); and several others. First off, the choice of C#/.NET Framework as the language on the client made sense to me. Having a background in C# meant after learning the platform-­specific features of a device there was one less item to spend time learning. Being able to develop everything in Visual Studio 2013 was an even bigger reason to go with a Xamarin solution for the iPhone. The problem with the mobile Web option is users want apps that integrate as deeply with their platform as possible. This is difficult with a mobile Web solution, but it’s easier with a native solution. Forgoing cross-platform and going with a vendor-directed solution didn’t make sense because it would require learning a new language for each platform.

Development Tools

Building an app for multiple platforms has typically required multiple development tools. Before the advent of Xamarin.iOS, development for the iPhone could only be done on a Mac using Xamarin Studio (previously MonoDevelop, which was a port of the open source SharpDevelop). While there’s nothing wrong with Xamarin Studio, developers typically want to stay within the same IDE as much as possible. Visual Studio 2013 used with Xamarin.iOS for Visual Studio lets you develop for Windows Azure, Windows Phone and the iPhone without having to leave the IDE you know and love.

Windows Azure

A mobile device can interact with Windows Azure in several ways. These include Windows Azure Virtual Machine (VM hereafter for brevity), Web Role, Windows Azure Web Sites and Windows Azure Mobile Services (WAMS).

A VM provides the most control of all the variables. You can make changes to your app and all of the server’s settings. This is great for those apps that require customization to the underlying OS settings, another app to be installed or other possible changes. This is referred to as Infrastructure as a Service (IaaS).

Windows Azure has a Cloud Service project type that encompasses a set of roles. Roles typically map to projects in Visual Studio. A Web Role is basically a Web project that has been bundled in a single deployable package that’s uploaded and runs within a VM. A Web Role provides Web UIs and can also have Web services within it. A Worker Role is a project that will continually run on the server. This is referred to as Platform as a Service (PaaS).

Windows Azure Web Sites are similar in concept to a Web Role. This solution will let an app host a Web site or project. The project can include Web services. These Web services can be called via a SOAP or REST call. Windows Azure Web Sites are a great solution for when an application only needs IIS.

The first three options require you to have complete knowledge of Web services—how to call them, storing them in the database, and the plumbing that’s in between the mobile device and the cloud. Microsoft has a solution that lets you quickly and easily store data in the cloud, handle push notifications and authenticate users quickly and easily: WAMS. Having built solutions with the other options, I thought it sensible to use WAMS, given the need for the least amount of plumbing as possible and its good cross-platform support. WAMS is sometimes described as the “back end that you don’t have to build.”

Windows Azure Mobile Services

WAMS lets you accelerate your mobile development efforts by providing storage, user authentication against multiple social networks, mechanisms to create logic on the server (with Node.js), push notifications, and a packaged client-side code library to ease create, read, update and delete (CRUD) operations against the data.

The first step in using WAMS is to create the mobile service and an associated database table. The database table isn’t strictly required. For more details on the setup process, see the Windows Azure tutorial at bit.ly/Nc8rWX.

Server Scripts Basic CRUD operations are available in WAMS via server operations. These are the delete.js, insert.js, read.js and update.js files, which are processed via Node.js on the server. For more information on Node.js in WAMS, see the article, “Work with server scripts in Mobile Services,” on the Windows Azure site at bit.ly/1cHASFA.

Let’s start by taking a look at the insert.js file in Figure 1. In the method signature, the “item” parameter contains the data that’s handed in. The members of the object map to the data object handed in from the client. The members of this object will make more sense after looking at the section on filling data from the client. The “user” parameter contains information regarding the connected user. In this example, the user must be authenticated. The app uses Facebook and Twitter for authentication, so the userId that’s returned has the form of “Network:12345678.” The “Network” part of the value contains the name of the network provider. In this example, either Facebook or Twitter is available, so one of these will be a part of the value. The number “12345678” is merely a representation of the userId. While Twitter and Facebook are used in this example, Windows Azure can also use a Microsoft or Google account.

Figure 1 The Insert.js File Used When a Golf Drive Is Inserted into the Cloud

function insert(item, user, request) {
  if ((!isNaN(item.StartingLat)) && (!isNaN(item.StartingLon)) &&
    (!isNaN(item.EndingLat)) && (!isNaN(item.EndingLon))) {
    var distance1 = 0.0;
    var distance2 = 0.0;
    var sd = item.StartingTime;
    var ed = item.EndingTime;
    var sdate = new Date(sd);
    var edate = new Date(ed);
    var res = user.userId.split(":");
    var provider = res[0].replace("'", "''");
    var userId = res[1].replace("'", "''");
    var insertStartingDate = sdate.getFullYear() + "-" +
       (sdate.getMonth() + 1) + "-" + sdate.getDate() + " " +
      sdate.getHours() + ":" + sdate.getMinutes() + ":" +
      sdate.getSeconds();
    var insertEndingDate = edate.getFullYear() + "-" +
      (edate.getMonth() + 1) + "-" + edate.getDate() + " " +
      edate.getHours() + ":" + edate.getMinutes() + ":" + 
      edate.getSeconds();
    var lat1 = item.StartingLat;
    var lon1 = item.StartingLon;
    var lat2 = item.EndingLat;
    var lon2 = item.EndingLon;
    var sp = "'POINT(" + item.StartingLon + " " + 
      item.StartingLat + ")'";
    var ep = "'POINT(" + item.EndingLon + " " + 
      item.EndingLat + ")'";
    var sql = "select Max(Distance) as LongDrive from Drive";
    mssql.query(sql, [], {
      success: function (results) {
        if ( results.length == 1)
        {
          distance1 = results[0].LongDrive;
        }
      }
    });
    var sqlDis = "select [dbo].[CalculateDistanceViaLatLon](?, ?, ?, ?)";
    var args = [lat1, lon1, lat2, lon2];
    mssql.query(sqlDis, args, {
      success: function (distance) {
        distance2 = distance[0].Column0;
      }
    });
    var queryString = 
      "INSERT INTO DRIVE (STARTINGPOINT, ENDINGPOINT, " +
      "STARTINGTIME, ENDINGTIME, Provider, UserID, " +
      "deviceType, deviceToken, chanelUri) VALUES " +
      "(geography::STPointFromText(" + sp + ", 4326), " +
      " geography::STPointFromText(" + ep + ", 4326), " +
      " '" + insertStartingDate + "', '" +
      insertEndingDate + "', '" + provider + "', " + userId + ", " +
      item.deviceType + ", '" + item.deviceToken.replace("'", "''") +
       "', " + "'" + item.ChannelUri.replace("'", "''") + "')";
    console.log(queryString);
    mssql.query(queryString, [], {
      success: function () {
        if (distance2 > distance1) {
          if (item.deviceType == 0) {
            push.mpns.sendFlipTile(item.ChannelUri, {
              title: "New long drive leader"
            }, {
                  success: function (pushResponse) {
                    console.log("Sent push:", pushResponse);
                  }
               });
            }
          if (item.deviceType == 1) {
            push.apns.send(item.deviceToken, {
              alert: "New Long Drive",
              payload: {
                inAppMessage: "Hey, there is now a new long drive."
              }
            });
          }
        }
      },
      error: function (err) {
        console.log("Error: " + err);
      }
    });
    request.respond(200, {});
  }
}

The first thing to do is test the code to validate the input. I want to verify that the latitudes and longitudes brought in are valid numbers. If not, the insert will exit immediately. The next step is to parse the passed-in userId to get the network provider and the numeric user identifier. The third step is to set up the dates so they can be inserted into the database. JavaScript and SQL Server have mismatched date/time representations, so these must be parsed and placed in the correct format.

Now a query needs to be done. A Node.js command to perform a CRUD statement calls mssql.query(command, parameters, callbacks). The “command” parameter is the SQL command that will be executed. The “parameters” parameter is a JavaScript array that matches up with the parameters specified in the command that’s set. The “callbacks” parameter contains the JavaScript callbacks to be used when the query is completed, depending on success or error. I’ll discuss the contents of a successful initial query in the section on push notifications.

Finally, the question of debugging comes up. How do you know what’s happening in the script? JavaScript has the console.log(info) method. When this method is called with the “info” parameter, the parameter is saved within the log file of the service, as shown in Figure 2. Note the built-in refresh functionality at the top right of the screen.

Log File Information in Visual Studio 2013
Figure 2 Log File Information in Visual Studio 2013

Once WAMS is set up, it can be managed via the windowsazure.com portal or Visual Studio.

Note: Calling from a WAMS script file to a method may result in an error with the default setup because they run in different schemas. Permissions might need to be granted depending on your specific situation. Jeff Sanders has a blog post about this issue at bit.ly/1cHQ4Cu.

Scale

Mobile apps can put a tremendous load on infrastructure, and luckily Windows Azure has several options to handle this. First, you have several alternatives for message queuing.

Queuing is available in Windows Azure via Service Bus as well as the Windows Azure Queue Service. Queuing lets you quickly store data without tying up the app. Under heavy load, an app can wait on a response from a remote data source. Instead of interacting directly with a data source, an app can store data in a queue, which will free the app to continue processing. Based on personal experience, using queuing can easily increase the scalability of an app. While this app doesn’t use queuing, it needs to be mentioned as an option depending on the load of the operations and the number of mobile devices accessing the system. Thankfully, both the Service Bus and the Windows Azure Queue Service have the necessary APIs for the server scripts in WAMS to access each of them.

Overall, queuing is a great solution for data-intensive apps. Another tool in the scaling toolbox is auto scale. Windows Azure lets you monitor the health and availability of an app from a dashboard. You can set up rules to notify an application administrator when the availability of services is degraded. Windows Azure lets an app scale up or down to match demand. By default, this feature is turned off. When it’s turned on, Windows Azure will periodically check the number of API calls on the service and will scale up if the number of calls is at or more than 90 percent of the API quota. Every day, Windows Azure will scale back down to the set minimum. The general rule is to set the daily quota to handle the expected daily traffic and allow Windows Azure to scale up as necessary. As this is being written, health, monitoring and auto scaling are available in preview.

Database

Data is the root of any app and the basis for nearly every business. You can use a hosted third-party database, a database service running with a VM, Windows Azure SQL Database and probably several other options. For the back-end database, I chose to use Windows Azure SQL Database over SQL Server running within a VM for several reasons. First, it has support for location-based services in the base product. Second, Windows Azure SQL Database is optimized for performance over a baseline installation of SQL Server on a client system. Finally, there’s no ongoing management of the underlying system.

Windows Azure SQL Database has the same point and geography database types as SQL Server, so it’s easy to calculate distances between two points. To make this easier, I wrote two stored procedures to calculate these distances. The SQL function Calculate­DistanceViaLatLon takes the floating point values of latitude and longitude. It’s designed to run within the WAMS insert.js script so it’s easy to calculate the distance of the incoming drive. The result can be compared with the current maximum drive within the system. The SQL function CalculateDistance takes two geography points and calculates the distance between them, as shown in Figure 3. The data is stored in the Drive table as SQL Server points.

Figure 3 SQL Function to Calculate Distance Between Two Points

 

CREATE FUNCTION [dbo].[CalculateDistanceViaLatLon]
(
  @lat1 float,
  @lon1 float,
  @lat2 float,
  @lon2 float
)
RETURNS float
AS
BEGIN
  declare @g1 sys.geography = sys.geography::Point(@lat1, @lon1, 4326)
  declare @g2 sys.geography = sys.geography::Point(@lat2, @lon2, 4326)
  RETURN @g1.STDistance(@g2)
END
CREATE FUNCTION [dbo].[CalculateDistance]
(
  @param1 [sys].[geography],
  @param2 [sys].[geography]
)
RETURNS INT
AS
BEGIN
  RETURN @param1.STDistance(@param2)
END

Figure 4 shows the table used to hold the data about the drive. Because the columns prefixed with “__” are columns particular to Windows Azure, every effort has been made to not use them. The columns of interest are StartingPoint, EndingPoint, Distance, deviceToken and deviceType. The StartingPoint and EndingPoint columns hold the starting and ending geographic points of a drive. The Distance column is a calculated column. It’s a float that uses the Calculate­Distance SQL function. The deviceToken and deviceType columns hold a token that identifies the device and the type of the device (Windows Phone-based or iPhone). The app currently only communicates back to theposting device if a new drive entered is the new leader. The deviceToken and deviceType columns could be used to communicate there’s a new leader and to regularly communicate other updates to competitors.

Figure 4 SQL Table to Hold Drive Data

CREATE TABLE [MsdnMagGolfLongDrive].[Drive] (
  [id]            NVARCHAR (255)
     CONSTRAINT [DF_Drive_id] 
     DEFAULT (CONVERT([nvarchar](255),newid(),(0))) 
     NOT NULL,
  [__createdAt]   DATETIMEOFFSET (3) CONSTRAINT
    [DF_Drive___createdAt] DEFAULT (CONVERT([datetimeoffset](3),
    sysutcdatetime(),(0))) NOT NULL,
  [__updatedAt]   DATETIMEOFFSET (3) NULL,
  [__version]     ROWVERSION         NOT NULL,
  [UserID]        BIGINT             NULL,
  [StartingPoint] [sys].[geography]  NULL,
  [EndingPoint]   [sys].[geography]  NULL,
  [DateEntered]   DATETIME           NULL,
  [DateUpdated]   DATETIME           NULL,
  [StartingTime]  DATETIME           NULL,
  [EndingTime]    DATETIME           NULL,
  [Distance]      AS                 ([dbo].[CalculateDistance]
    ([StartingPoint],[EndingPoint])),
  [Provider]      NVARCHAR (20)      NULL,
  [deviceToken]   NVARCHAR (100)     NULL,
  [deviceType] INT NULL,
  PRIMARY KEY NONCLUSTERED ([id] ASC)
);

Dynamic Schema

One of the great features of WAMS is that a database table schema is dynamic by default. It’s modified based on the information sent from the mobile device to the client. As you move from development to production, this should be turned off. The last thing you want is some type of schema change to a running system due to a programming mistake. This can easily be done by going to Configure in the WAMS section of the Windows Azure Portal and turning the “dynamic schema” option to off.

Accessing Data

Accessing data with a mobile device with an unreliable and a relatively high-latency network is significantly different from accessing data over a wired connection and relatively low-latency network. There are two general rules to get data with a device. First, data access must be done asynchronously. Given the unreliable nature and high latency of mobile networks, locking the UI thread in any way is a really bad idea. Users don’t understand why the UI is stalled. If getting data takes too long, the device OS will assume the app is hung and kill it. The second rule is that the transferred data must be relatively small. Sending too many records to a mobile device will cause problems due to the low-speed, high-latency networks common in mobile provider systems and the fact that mobile device CPUs are optimized more for low power consumption than for processing data like a laptop or desktop CPU. WAMS addresses both these issues. Data access is asynchronous and queries are automatically done via a paging algorithm. To show this, I’ll look at two operations, an insert and a select.

Using a Proxy Any developer who has called REST-based services knows about the problem of using REST due to its lack of a built-in proxy service. This makes working with REST somewhat mistake-prone. It’s not impossible—just a little more difficult to work with than SOAP. To make development easier, you can create a proxy locally. The proxy for this example is shown in Figure 5. The properties of an object instance can be accessed in the server scripts.

Figure 5 A Proxy to Work with REST

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
namespace Support
{
  public partial class Drive
  {
    public Drive() {
      DeviceToken = String.Empty;
      ChannelUri = String.Empty;
    }
    [JsonProperty(PropertyName="id")]
    public string Id { get; set; }
    [JsonProperty(PropertyName = "UserID")]
    public Int64 UserID { get; set; }
    [JsonProperty(PropertyName = "Provider")]
    public string Provider { get; set; }
    public double StartingLat { get; set; }
    public double StartingLon { get; set; }
    public double EndingLat { get; set; }
    public double EndingLon { get; set; }
    [JsonProperty(PropertyName = "StartingTime")]
    public DateTime StartingTime { get; set; }
    [JsonProperty(PropertyName = "EndingTime")]
    public DateTime EndingTime { get; set; }
    [JsonProperty(PropertyName = "Distance")]
    public double Distance { get; set; }
    [JsonProperty(PropertyName = "deviceType")]
    public int deviceType { get; set; }
    [JsonProperty(PropertyName = "deviceToken")]
    public string DeviceToken { get; set; }
    [JsonProperty(PropertyName = "ChannelUri")]
    public string ChannelUri { get; set; }
  }
}

Query Data Querying data in Windows Azure is actually simple. The data will be returned via a call through a LINQ query. Here’s a call to run a simple query to return the data: 

var drives = _app.client.GetTable<Support.Drive>();
var query = drives.OrderByDescending(
  drive => drive.Distance).Skip(startingPoint).Take(PageSize);
var listedDrives = await query.ToListAsync();

In this example, I need the list of longest drives starting at the top of the list and going down. This is then bound to a grid in both a Windows Phone-based device and the iPhone. While the data binding part is different, retrieving the data is exactly same.

Note that in the preceding query, there’s no Where method called, but this could easily be done. Also, the Skip and Take methods are used to show how the app could easily add in paging. Figure 6 shows the scoreboard on a Windows Phone-based device and on an iPhone.

The Scoreboard As Depicted on a Windows Phone-Based Device and an iPhone
Figure 6 The Scoreboard As Depicted on a Windows Phone-Based Device and an iPhone

Inserting Data

Inserting a record is easy in WAMS. Just create an instance of the data object and then call the InsertAsync method on the client object. The code for the insert in a Windows Phone-based device is shown in Figure 7. The code for performing an insert in Xamarin.iOS is similar and only differs in the areas of the deviceType, ChannelUri and DeviceToken.

Figure 7 Inserting Data (Windows Phone-Based Device)

async void PostDrive()
{
  Drive d = new Drive();
  d.StartingLat = first.Latitude;
  d.StartingLon = first.Longitude;
  d.EndingLat = second.Latitude;
  d.EndingLon = second.Longitude;
  d.StartingTime = startingTime;
  d.EndingTime = endingTime;
  d.deviceType = (int)Support.AppConstants.DeviceType.WindowsPhone8;
  d.ChannelUri = _app.CurrentChannel.ChannelUri.ToString();
  try
  {
    await _app.client.GetTable<Support.Drive>().InsertAsync(d);
  }
  catch (System.Exception exc)
  {
    Console.WriteLine(exc.Message);
  }
}

Sharing Code Between Platforms

Sharing code between platforms is an important consideration and can be done in several ways with the cross-platform capabilities of C# and Xamarin. The mechanism used is determined by the exact scenario. I needed to share non-UI logic. To do that, there are two options: Portable Class Libraries (PCL) and linked files.

Portable Class Libraries Lots of platforms use the .NET Framework. These platforms include Windows, Windows Phone, Xbox, Windows Azure and others supported by Microsoft. When the .NET Framework initially shipped—and up through several iterations afterward—.NET code would need to be recompiled for various platforms. PCLs solve this problem. With a PCL project, you set up a library to support a defined set of APIs available for the target platforms. This choice of platforms is set in the class library’s project settings.

Along with Microsoft’s support for PCLs, last fall Microsoft changed its PCL licensing to allow support on non-Microsoft platforms. This allowed Xamarin Inc. to provide support for the defined Microsoft PCLs in the iOS and Android platforms and on OS X. Documentation on using PCLs is readily available.

Linked Files PCLs are a great solution for cross-platform development. However, if a feature is included on one platform but not on another, linked files become the alternative way to share code. A linked file setup includes a basic .NET class library, the platform-specific class libraries and the platform application project. The .NET class library has the general code that’s shared across platforms.

The platform-specific library contains two types of files. These are the linked files from the generic .NET class library and code that would have common APIs but different platform-specific implementations. The idea is to “Add As Link” a file from the class library project into the platform-specific class library. This is shown in Figure 8.

Using Linked Files
Figure 8 Using Linked Files

Other Options PCLs and linked files are just two of the options you can use to share code. Other options include partial classes, if/def compiler options, the observer pattern, Xamarin.Mobile (and similar libraries), other libraries available via NuGet (or the Xamarin Component Store) and more.

Partial classes allow for the multiple class files to be shared across the shared class library and the platform-specific class library. By default, the namespaces will be different between the .NET class library and the platform-specific library. The biggest issue to be aware of with partial classes is that the namespaces must match. Not having the namespaces match is a common mistake made with partial classes.

Visual Studio allows code to be compiled into or out of code via if/then compiler options. Along with this, the platforms can be defined as conditional compilation symbols. These are set up in the project’s properties, as shown in Figure 9, where the #if directive is used to conditionally compile code for Windows Phone.

Defining a Platform as a Conditional Compilation Symbol
Figure 9 Defining a Platform as a Conditional Compilation Symbol

Xamarin.Mobile is a set of libraries with common APIs. The libraries are available for Windows Phone, iOS and Android. Xamarin.Mobile currently supports location services, contacts and the camera. I used Xamarin.Mobile geolocation APIs in this app.

To determine location, the geolocator object is a wrapper for the platform-specific geolocation object. Here, it uses the C# 5.0 async-style syntax. Once a location is determined, a call is made into .ContinueWith and processing occurs:

geo = new Geolocator();
...
await geo.GetPositionAsync(timeout: 30000).ContinueWith(t =>
  {
    first = t.Result;
    LandingSpot.IsEnabled = true;
  }, TaskScheduler.FromCurrentSynchronizationContext());

Note that devices tend to provide geolocation as an approximation. As a result, not every recorded distance will be perfectly accurate.

When apps are built, developers tend to think of higher logical layers of an app calling down into lower levels. For example, a user can touch a button that triggers a location detection. The problem comes when the logical lower level of an app needs to call into a higher layer. A simple solution is to pass a reference from the higher level into a lower level. Unfortunately, this will almost definitely keep the lower-level code from being shared across platforms. This can be overcome with events. Fire an event in the lower level that’s processed in a higher level. This is the basis of the observer pattern.

Numerous third parties have created libraries that can be used across platforms. You can find these with NuGet and the Xamarin Component Store.

Push Notifications

Sometimes the server app needs to communicate with the mobile device. This can be done via WAMS or the Notification Hub. WAMS is a great solution when sending a small number of messages. The Notification Hub is designed for sending messages to a large number of devices, such as “premium customers” or “all customers in the state of California.” I’ll discuss the WAMS option.

You can call WAMS push notifications to a mobile device within the server scripts. Windows Azure handles many of the complex parts of push notifications, but it can’t abstract every difference in the way messages are sent to every different platform. Thankfully, the differences are slight.

The mpns object is used to send messages via the Microsoft Push Notification Service (MPNS). The mpns object contains four members. These members are sendFlipTile, sendTile, sendToast and sendRaw. Each of these members has a similar signature. The first parameter is the channel that will be used to communicate. The second parameter is a JSON object with the parameters to be sent to the device. The third parameter is a set of callbacks that occur on the success or failure of the request. The following code using the mpns object is used in the Windows Azure server scripts to send a message if there’s a new leader in the longest drive contest:

push.mpns.sendFlipTile(item.ChannelUri, {
  title: "New long drive leader"
}, {
    success: function (pushResponse) {
      console.log("Sent push:", pushResponse);
    }

The result is the update to the tile as shown in Figure 10. Notice in the image how the tile has been updated to say “New long drive leader.”

A Push Message Showing a New Long Drive Leader
Figure 10 A Push Message Showing a New Long Drive Leader

You use an apns object to send messages to the Apple Push Notification Services (APNS) in the WAMS scripts. It’s conceptually similar to the mpns object. The member of most interest is the send method. It has a signature similar to the mpns send methods. The signature contains three parameters: the deviceToken, which uniquely identifies a device; a JSON-based parameter object; and a final parameter that’s a set of callbacks.

Here’s the code showing how the apns object is used to send a “new leader” message to an iOS device:

push.apns.send(item.deviceToken, {
  alert: "New Long Drive",
  payload: {
    inAppMessage: "Hey, there is now a new long drive."
  }
});

Figure 11 shows the code to be added to the AppDelegate.cs file to handle the message sent to the iPhone. In this example, a UIAlertView is displayed to the user.

Figure 11  Processing a Message on an iPhone

public override void RegisteredForRemoteNotifications(
  UIApplication application, NSData deviceToken)
{
  string trimmedDeviceToken = deviceToken.Description;
  if (!string.IsNullOrWhiteSpace(trimmedDeviceToken))
  {
    trimmedDeviceToken = trimmedDeviceToken.Trim('<');
    trimmedDeviceToken = trimmedDeviceToken.Trim('>');
  }
  DeviceToken = trimmedDeviceToken;
}
public override void ReceivedRemoteNotification(
  UIApplication application, NSDictionary userInfo)
{
  System.Diagnostics.Debug.WriteLine(userInfo.ToString());
  NSObject inAppMessage;
  bool success = userInfo.TryGetValue(
    new NSString("inAppMessage"), out inAppMessage);
  if (success)
  {
    var alert = new UIAlertView("Got push notification",
      inAppMessage.ToString(), null, "OK", null);
    alert.Show();
  }
}

If needed, you can use a gcm object to send messages to the Google Cloud Messaging (GCM) platform.

One significant difference between the Windows and Apple push notifications (and Google notifications) is how the client mobile system handles these messages. A full listing of client systems is included in the Xamarin.iOS project in the AppDelegate.cs file in the accompanying code download.

And that’s it. Good luck with your mobile app development and your golf game!


Wallace B. McClure graduated from the Georgia Institute of Technology (Georgia Tech) with bachelor’s and master’s degrees in electrical engineering. He has done consulting and development for companies large and small. McClure has authored books on iPhone programming with Xamarin.iOS; Android programming with Xamarin.Android; application architecture; ADO.NET and SQL Server; and AJAX. He is a Microsoft MVP, ASPInsider, Xamarin MVP and Xamarin Insider. He’s also a partner in Scalable Development Inc. His training materials for iOS and Android are available via Learn Now Online. He’s on Twitter at twitter.com/wbm.

Thanks to the following technical experts for reviewing this article: Kevin Darty (independent contractor) and Brian Prince (Microsoft)