Xamarin.Android Binding Generators

UPDATE

This NuGet is no longer required to get nice parameter names in the binding. With the release of Cycle 8, Xamarin has provided a way to use the class-parse tool that comes with the Xamarin release.

More information can be found on the binding docs, but the gist of it is that you just need to add a new element to your project file:

<PropertyGroup>
  <AndroidClassParser>class-parse</AndroidClassParser>
</PropertyGroup>


Build status

A MSBuild Task to make Xamarin.Android binding projects easier.

Currently, there is no nice way to set the parameter names for methods when binding Xamarin.Android libraries. This tiny little NuGet adds a .targets file to the Xamarin.Android Binding project.

The .targets file adds a MSBuild task which will then read the jar elements and automatically generate the transform files needed to set the parameter names for all the bound methods.

You can view the project source for this on GitHub.

Usage

Using this is very simple, just install the NuGet:

PM> Install-Package Xamarin.Android.Bindings.Generators

Then, rebuild! That is all!

As this is just used for binding projects, it shouldn’t be installed
into Xamarin.Android app projects.

Features

This is currently what is available in the first release:

  • Automatic parsing of InputJar and EmbeddedJar
  • Handles multiple input files
  • Finds available parameter names
  • Missing parameter names are still meaningful (uses type names)
  • Shouldn’t give matched-no-nodes warnings
  • No manual intervention necessary

Microsoft Band SDK for Xamarin.Android

Xamarin and the Microsoft Band

Microsoft finally released an SDK for the Microsoft Band! Yay! The Band is very much designed for health and fitness uses with many sensors. But, it is certainly not limited to health uses. Because we have access to the sensors and can interact with the device, we can use the Band to enhance and extend our app. Working with Band essentially means communicating with the Band from the app installed on the device.

There are 4 main areas that we can interact with the Band:

  1. Reading information from the Sensors
  2. Creating themed app Tiles
  3. Sending Messages to the Band
  4. Theming and Personalizing the Band

The Library and Source Code

The Microsoft Band SDK is still in preview and is available on the official Developer page. When working with Xamarin.Android, we use the component from the Xamarin Component Store.

The binding 4, where you can fork, extend and improve. I will also be looking to create a PCL version that can be used in cross-platform apps, such as those using the Xamarin.Forms framework.

There are currently two demo apps to look at:

  1. Android Feature Demo – demonstrates all the features for Android
  2. Rotating Hand Demo – shows a simple example using the accelerometer (port from Iris Classon)

Getting Started

Before we can do anything with the Band, we need to be able to connect to it. This involves updating the Band and configuring the Xamarin.Android project.

First, we need to make sure that the latest Microsoft Health app is installed on the Android device and the Band updated with the latest firmware (done through the Health app). The Band needs to be paired with the Android device, and, the Health app needs to be synced with the Band.

Next, we need to ensure that we have set up our Xamarin.Android project with permissions to interact with the Band. The minimum supported Android version is 4.2, or level 17.

Finally, we need to make sure that we have permission to communicate with the Band over Bluetooth. This is easy to add using the [UsesPersmission] attributes:

    [assembly: UsesPermission(Android.Manifest.Permission.Bluetooth)]
    [assembly: UsesPermission(Microsoft.Band.BandClientManager.BindBandService)]

Connecting to a Band

Now that we have the Band, the device and the Xamarin.Android project set up, we can start connecting to the Band.

The first thing we want to do is get a list of the Bands paired with the device:

    var bands = BandClientManager.Instance.GetPairedBands();

Then we need to get an instance of IBandClient that will be used to communicate with the Bands:

    var client = BandClientManager.Instance.Create(this, bands[0]);

Once we have the client, we can connect to the actual Band:

    var result = await client.ConnectTaskAsync();
    if (result != ConnectionResult.Ok) {
        // we couldn't connect
    }

When we no longer require a connection to the Band, we can disconnect:

    await client.DisconnectTaskAsync();

Working with Sensors

Working with Microsoft Band Sensors

The main feature of the Band is the many sensors. The sensors that are available to us are the:

  • Accelerometer
    Provides X, Y, and Z acceleration in meters per second squared (m/s²) units.
  • Contact
    Provides a way to let the developer know if someone is currently wearing the device.
  • Distance
    Provides the total distance in centimeters, current speed in centimeters per second (cm/s), current pace in milliseconds per meter (ms/m), and the current pedometer mode (such as walking or running).
  • Gyroscope
    Provides X, Y, and Z angular velocity in degrees per second (°/sec) units.
  • Heart Rate
    Provides the number of beats per minute, also indicates if the heart rate sensor is fully locked onto the wearer’s heart rate.
  • Pedometer
    Provides the total number of steps the wearer has taken.
  • Skin Temperature
    Provides the current skin temperature of the wearer in degrees Celcius.
  • Ultraviolet (UV)
    Provides the current ultraviolet radiation exposure intensity.

Subscribing to sensors has an impact on the Band’s battery life. The use of each sensor requires power draw (some more than others), so we should only subscribe when the data is absolutely needed for our app.

Connecting to the sensors involves a few steps, namely getting hold of the sensor, attaching an event handler and then starting the sensor listener. So first, we want to get hold of a sensor:

    var accelerometer = client.SensorManager.CreateAccelerometerSensor();

Then we want to attach an event handler to the sensor so that we can be notified of updates. This is called from another thread and we need to be able to handle a high volume of events:

    accelerometer.ReadingChanged += (sender, args) => {
        var yReading = args.SensorReading.AccelerationY;
        RunOnUiThread(() => {
            label.Text = yReading;
        });
    };

Finally, we start listening to the sensor. Some sensors (namely the accelerometer and gyroscope) require a listening interval, or sample rate, which could be 16ms, 32ms or 128ms:

    await accelerometer.StartReadingsTaskAsync(SampleRate.Ms16);

When we want to stop listening, all we have to to is call one method:

    await accelerometer.StopReadingsTaskAsync();

Creating App Tiles

Creating App Tiles on the Microsoft Band

Tiles allow us to create app specific experiences on the Band. The Band supports up to 13 separate tiles, and will allow us to create as many tiles as there is space for.

If we want to find out how many tiles we can still create, we can use the TileManager:

    var capacity = await client.TileManager.GetRemainingTileCapacityTaskAsync();

Each tile consists of several properties:

  • a title or name
  • a tile icon
  • a small/badge icon
  • a theme/colour set

Creating App Tiles on the Microsoft Band

The band is required to have an ID (UUID), a title and an icon. If no small icon is provided, notification/badge numbers will not appear on the tile when a message is received. Each tile can have a custom theme, and if none is provided, then the current Band theme will be used.

The Band does not support colour icons, but only white, alpha blended icons. Also, each tile icon must be be 46×46 pixels. The small icon should be 24×24 pixels. Because the Band does not directly support the native Android bitmap, we use the BandIcon type:

    var options = new BitmapFactory.Options();
    options.InScaled = false;
    var icon = await BitmapFactory.DecodeResourceAsync(Resources, Resource.Raw.tile, options)
    var tileIcon = new BandIcon(icon);

If we want to create a new tile, we use the BandTile.Builder:

    var uuid = Java.Util.UUID.RandomUUID();
    var tileName = "My Tile";
    var tile = new BandTile.Builder(uuid, tileName, tileIcon).Build();

We can also set a small icon and a custom theme:

    var tile = new BandTile.Builder(uuid, tileName, tileIcon)
        .SetTileSmallIcon(badgeIcon)
        .SetTheme(BandTheme.TuxedoTheme)
        .Build();

To add a tile to the band, we make use of the TileManager property:

    var success = await client.TileManager.AddTileTaskAsync(this, tile);

Removing a tile is also a single method on the tile manager:

    var success = await client.TileManager.RemoveTileTaskAsync(tile);

We can either pass the tile to the remove method, or we can just use the ID:

    var success = await client.TileManager.RemoveTileTaskAsync(uuid);

If we want to get a list of all the tiles on the device for our app, we can query the tile manager

    var tiles = await client.TileManager.GetTilesTaskAsync();

Sending Messages

We can display a notification for a tile using the NotificationManager property. There are three types of notifications:

  1. Dialogs
    A quick popup that the user can dismiss and the message will not be persisted on the Band. Dialogs have an accompanying single, short vibration.
  2. Messages
    A persistent notification that will remain in the tile. Up to 8 messages can be persisted before the oldest ones are removed to make room. If the tile has a small icon, the number of unread messages will appear on the tile. Messages may also have an accompanying dialog.
  3. Vibrations
    A haptic feedback mechanism that can be used to alert the user to a notification on the Band.

Each dialog consists of several properties:

  • a tile ID
  • a title
  • a body

A message is similar to a dialog, but contains an extra property:

  • a tile ID
  • a title
  • a body
  • a time stamp

To display a dialog, we need the tile ID, the title and a body:

    var title = "My Message";
    var body = "My explanation for the message.";
    await client.NotificationManager.ShowDialogTaskAsync(uuid, title, body);

Displaying messages is very similar, except there are a few extra parameters:

    var date = DateTime.Now;
    await client.NotificationManager.SendMessageTaskAsync(uuid, title, body, date);

If we want a dialog to appear along with the message, we can add an additional parameter:

    await client.NotificationManager.SendMessageTaskAsync(uuid, title, body, date, true);

To cause the Band to vibrate, we can specify a type of vibration:

    VibrationType vibration = ...
    await client.NotificationManager.VibrateTaskAsync(vibration);

Working with Themes

Working with Themes on the Microsoft Band

Using a theme on the devices could be the tile theme or changing the wallpaper. We can customize the Band in three ways:

  1. App Tile Theme
    This theme applies to only the app, and only when the app is open.
  2. Band Theme
    This theme applies to all the tiles and apps on the Band, except where the app has a custom theme. The tile on the Start Strip will use the Band theme, but once the app opens, it will use the app theme.
  3. Me Tile
    This is the background image on the Me tile. This tile is the first, large tile in the Start Strip.

Whether we customize our app tile theme, or the overall theme, we are provided a few, pre-defined themes. These include:

  • CornFlower
  • Cyber
  • Joule
  • Orchard
  • Penguin
  • Storm
  • Tuxedo
  • Violet

There are two parts to a theme, the tile colours and the app colours. The tile colours are:

  • Base
    This is the normal colour of the tile in the Start Strip. This colour is also used for the app’s back/app bar.
  • High Contrast
    This is the colour of the tile when there is a notification available.
  • Lowlight
    This is the colour of the visual feedback, when the user taps the tile or any buttons in the app.

The app colours are:

  • Highlight
    This is the colour of the app’s header text, such as the title in messages or dialogs.
  • Secondary Text
    This is the colour for other informational text in the app, whether the secondary header or date information in messages.
  • Muted
    This is the colour used when displaying notifications when the app is open, such as when a message is received.

When working with themes, we use the PersonalizationManager property. This provides a way to work with the theme or the Me tile background. In order to get the current theme that is set, we use the GetThemeTaskAsync method:

    var theme = await client.PersonalizationManager.GetThemeTaskAsync();

When we want to create a new theme, we can either modify an existing theme, or we can create a new BandTheme instance:

    var theme = new BandTheme {
        BaseColor = ...,
        HighContrastColor = ...,
        LowlightColor = ...,
        HighlightColor = ...,
        SecondaryTextColor = ...,
        MutedColor = ...
    }

Once we have our theme created, or just modified, we can apply it to the entire Band:

    await client.PersonalizationManager.SetThemeTaskAsync(theme);

If we want to use the theme with a single app tile, we must set it during the construction of the tile:

    var tile = new BandTile.Builder(uuid, tileName, tileIcon)
        .SetTileSmallIcon(badgeIcon)
        .SetTheme(theme)
        .Build();

Customizing the Me Tile

One of the first thing the user sees when looking at the Band is the Me tile. This is the first tile on the Start Strip and is where the date lies.

The background image on this tile must be exactly 210×102 pixels and should be in the RGB565 bitmap format. The bitmap instance type used by the API is Android.Graphics.Bitmap, the same type ised by the usual Android widgets.

If we want to access the image already on the Band, we can use the GetMeTileImageTaskAsync method:

    var bitmap = await client.PersonalizationManager.GetMeTileImageTaskAsync();

To set a new image, we use the SetMeTileImageTaskAsync method:

    await client.PersonalizationManager.SetMeTileImageTaskAsync(bitmap);

OneDrive Picker for Android

The picker is the easiest way to browse, select, open and save OneDrive files an Android app with OneDrive.

OneDrive Picker in Action

The source can be found on GitHub and there is a component on the Xamarin Component Store.

Requirements

Before we can access the OneDrive, we will need to get an App ID. Register the app here to get an App ID (Client ID).

The OneDrive picker library is supported at runtime for Android API revision 14 and greater.

The picker requires the OneDrive app to be installed, in order to function. If the OneDrive app is not installed, the user will be prompted to download the app when either the StartPicking() or StartSaving() method is invoked.

Opening files

Our app needs to give the user a way to start opening files from OneDrive. Here we set up a click handler that launches the open picker:

    // keep a reference to the picker
    private IPicker picker;

    protected override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);

        // ...

        // create a picker
        picker = Picker.CreatePicker("ONEDRIVE_APP_ID");

        button.Click += delegate {
            // start the picker activity
            picker.StartPicking(this, LinkType.DownloadLink);
        };
    }

    protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
    {
        // get the results from the picker activity
        var result = picker.GetPickerResult(requestCode, resultCode, data);
        if (result != null) {
            // use the result
        } else {
            // continue as normal
            base.OnActivityResult(requestCode, resultCode, data);
        }
    }

Start Picking

The picker can be configured to return a URL for the selected file in one of these
formats:

  • LinkType.DownloadLink – the URL is valid for 1 hour and can be used to download a file
  • LinkType.WebViewLink – the readonly URL is valid until the user deletes the sharing link

Picker Result Object

In addition to the filename and link for the file, you can access several other properties on the IPickerResult object that provide more details about the file selected:

    public interface IPickerResult
    {
        Uri Link { get; }
        LinkType LinkType { get; }
        string Name { get; }
        long Size { get; }
        // keys: "small", "medium", and "large"
        IDictionary<string, Uri> ThumbnailLinks { get; }
    }

Saving Files

Similar to when opening files, we should provide a way for the user to save a file to OneDrive. Before we can upload a file, we need a filename for OneDrive and the path to the file on the local file system:

    private ISaver saver;

    protected override void OnCreate (Bundle savedInstanceState)
    {
        base.OnCreate (savedInstanceState);

        // ...

        // create the saver
        saver = Saver.createSaver("ONEDRIVE_APP_ID");

        button.Click += delegate {
            // launch the saver
            saver.startSaving(this, "FILENAME", "file://PATH/TO/FILE");
        }
    };

    protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
    {
        try {
            // the file saved successfully
            saver.HandleSave (requestCode, resultCode, data);
        } catch (SaverException ex) {
            // there was an error
        }
    }

Start Saving

The saver currently supports the content:// and file:// file URI scheme. If a different URI scheme is used, the saver will return a NoFileSpecified error type.

Saver Result

The error message provided by DebugErrorInfo is primarily for development and debugging and can change at any time. When handling errors, you can use ErrorType to determine the general cause of the error.

Saver Error Types

When the saver is unable to complete saving a file and throws an exception, it provides a SaverError through the ErrorType property that indicates one of a set of possible error types:

    public enum SaverError {
        Unknown,
        Cancelled,
        // The OneDrive account did not have enough space
        OutOfQuota,
        InvalidFileName,
        NoNetworkConnectivity,
        // The Uri to the file could not be accessed
        CouldNotAccessFile,
        // No file was specified to be saved, 
        // or the file URI scheme was not supported
        NoFileSpecified
    }

Binding Flurry Analytics with Xamarin.Android

This week, I needed to use Flurry Analytics in my Xamarin.iOS and my Xamarin.Android apps, but there was no .NET SDK for these platforms. Flurry did provide a SDK for each platform, but it was the native libraries, which cannot be used directly in .NET. Given this, I decided to bind those native libraries using Xamarin.iOS and Xamarin.Android.

I have split this project up into four parts:

  1. Introduction and Pre-requisites
  2. Xamarin.iOS binding
  3. Xamarin.Android binding
  4. Flurry.Analytics in the wild

After binding the iOS SDK for Flurry Analytics, we are going to move onto the Android release. This task needs only one thing from the downloaded SDK:

  • [Android-sdk-path]/Android 4.1.0/FlurryAnalytics/FlurryAnalytics-4.1.0.jar

Of course, the version numbers may change for later releases. The java archive file (.jar) is going to be used to generate the .NET interfaces and enums, and then be embedded in the resulting assembly.

Just before we do the real binding, we should just create our C# solution for the Xamarin.Android binding. Just create a new project/solution and select the Android “Bindings Library” template. For my project name I used Flurry.Analytics.Android, but you could use anything.

The default project has a few files and directories in it, we will have a look at each one in depth as we go through the binding steps:

  • /Additions/ (this allows you to add arbitrary C# to the generated classes
    before they are compiled)
  • /Properties/AssemblyInfo.cs (the usual assembly information attributes)
  • /Jars/ (this directory is for Android .jars)
  • /Transforms/EnumFields.xml (this allows you to map Java int constants to C# enums)
  • /Transforms/EnumMethods.xml (this allows changes to method parameters and return types from Java int constants to C# enums)
  • /Transforms/Metadata.xml (this allows changes to be made to the final API)

Creating the Initial Binding

This step will involve just adding the jar to the project, and letting the compiler do it’s thing, but we will almost always have to go in and tweak a few things in the Metadata.xml.

So, first things first, add the jar file to the project and compile. The build will probably fail with error messages containing single character methods and types. This is because the actual jar file has been obfuscated. However this can easily be fixed.

The way the generation works is that there is a two step process, there is a tool that generates a bunch of xml files from the jar file. These xml files are then used to generate C# code. The Metadata.xml sits in between the two steps and can be used to transform the generated xml before the C# generation.

Tweaking the Generated Binding (Manifest.xml)

As we can see that the build fails on obfuscated types, we can remove these. The removal is safe as we are only preventing the .NET binding from being created, not actually removing the underlying Java members. And, as these items have been obfuscated, we can safely assume that we aren’t supposed to be accessing them anyway.

But, instead of removing each member or type that appears, we can use a great tool that will decompile the jar file and show us exactly what types are internal and what types we should be binding. I use JD-GUI, which is a free Java decompiler. They also have a nice online version, JD-Online. What we can do is to just upload the jar file here and see what’s inside:

  • com.flurry.android.impl.analytics.*
  • com.flurry.android.*
  • com.flurry.sdk.*

As we can see, the impl and sdk branches contain internal and obfuscated types, so we can remove those:

<remove-node path="/api/package[starts-with(@name, 'com.flurry.sdk')]" />
<remove-node path="/api/package[starts-with(@name, 'com.flurry.android.impl')]" />

After this, the build should now succeed and we will have an assembly that we can use. However, there are still some types that we can remove to clean the API a bit: InstallReceiver and Constants. InstallReciever is not used by the consumer, so this is safe to remove, but Constants is still used. If we remove this, then the consumer will not have access to the values on the type. We can see that Constants just contains the values representing male, female and unknown.

To remove the InstallReciever, we can add this line to Metadata.xml:

<remove-node path="/api/package[@name='com.flurry.android']/class[@name='InstallReceiver']" />

For the Constants type, we will do something different.

Managing the Enumerations (Additions & EnumFields.xml)

As we clean up the API, we want to remove the Constants type and replace it with an enum. One way to do this is to use the EnumFields.xml:

<mapping clr-enum-type="Flurry.Analytics.Gender" jni-interface="com/flurry/android/Constants">
    <field clr-name="Male" jni-name="MALE" value="1" />
    <field clr-name="Female" jni-name="FEMALE" value="0" />
    <field clr-name="Unknown" jni-name="UNKNOWN" value="-1" />
</mapping>

This will generate a nice enum for us, but this does leave an unused interface IConstants. As this is a very small library, we can do this mapping slightly differently. First we remove the entire Constants type in the Metadata.xml:

<remove-node path="/api/package[@name='com.flurry.android']/interface[@name='Constants']" />

Then, we can create the enum in the /Additions/ directory. To do this, add a new file (for example called FlurryAgent.cs) under this directory and add the enum:

public enum Gender
{
    Male = 1,
    Female = 0,
    Unknown = -1
}

Now there is one last thing to do before the API definition is complete. There is a SetGender method on the type FlurryAgent, which takes the type sbyte. It is not intuitive to use the Gender enum here, so we can fix this in a two step process. First we will create an overload in the FlurryAgent.cs file that accepts a Gender enum member as an argument:

public partial class FlurryAgent
{
    public static void SetGender(Gender gender)
    {
        FlurryAgent.SetGender ((sbyte)gender);
    }
}

And, what we can do is also hide the original member as the new overload is good enough:

<attr path="/api/package[@name='com.flurry.android']/class[@name='FlurryAgent']/method[@name='setGender']" name="visibility">internal</attr>

And with this, our binding is complete, although we can do a few nice changes to the namespace and parameter names.

Changing Parameter Names

Now that we have the binding complete, we can see that it is using the namespace Com.Flurry.Android, which is no .NET-like at all. We can change this to something better:

<attr path="/api/package[@name='com.flurry.android']" name="managedName">Flurry.Analytics</attr>

This maps the com.flurry.android package name to the neat Flurry.Analytics namespace. One last thing is to fix the parameter names. Sometimes you can use the JavaDocs, but in this case, I couldn’t get it to work. Doing it manually is not hard, but it is boring and time consuming, but here are a few.

This is the usual mapping:

<attr path="/api/package[@name='com.flurry.android']/class[@name='FlurryAgent']/method[@name='onStartSession']/parameter[@name='p0']" name="name">context</attr>
<attr path="/api/package[@name='com.flurry.android']/class[@name='FlurryAgent']/method[@name='onStartSession']/parameter[@name='p1']" name="name">apiKey</attr>
<attr path="/api/package[@name='com.flurry.android']/class[@name='FlurryAgent']/method[@name='onEndSession']/parameter[@name='p0']" name="name">context</attr>

If there are complicated parameter types (note the &lt;):

<attr path="/api/package[@name='com.flurry.android']/class[@name='FlurryAgent']/method[@name='logEvent']/parameter[@name='p1' and @type='java.util.Map<java.lang.String, java.lang.String>']" name="name">parameters</attr>

If there are overloads with different parameter types:

<attr path="/api/package[@name='com.flurry.android']/class[@name='FlurryAgent']/method[@name='onError']/parameter[@name='p2' and @type='java.lang.Throwable']" name="name">exception</attr>
<attr path="/api/package[@name='com.flurry.android']/class[@name='FlurryAgent']/method[@name='onError']/parameter[@name='p2' and @type='java.lang.String']" name="name">errorClass</attr>

After doing this for all the members, the binding is now complete.

Finishing Up

So far we have defined our API, added any additional logic or types, added the native library and added parameter names. This is all that is needed, so our library should build fine now and we should be able to use it in an Xamarin.Android app:

// the methods
FlurryAgent.StartSession(this, "PQSZJRK4B5BW8Q7YQQXF");

// the properties that we changed back into methods
string version = FlurryAgent.ReleaseVersion;

// the extra method that we added
FlurryAgent.SetGender (Gender.Male);

Bing Maps iOS SDK

The Bing Maps iOS SDK is a control for embedding maps directly into native iOS apps. With a rich, gesture-based UI, the SDK provides support for adding pushpins and other overlays to the maps. The SDK also allows your device location to be displayed and provides the ability to retrieve contextual information for a given map location.

This project provides a managed binding of the native library for Xamarin.iOS.

Sadly, after getting all this nice stuff going, I found the actual native library to be very buggy and often caused the app to crash. For example, if the push pins were animated, tapping them caused the app to crash. As a result, I won’t be pushing this to the stores.

Getting Started

Before you can use the map control, you will need a Bing Maps Key from the Bing Maps Portal. The process is described in the MSDN documentation, Getting a Bing Maps Key, and is very simple with only four steps.

A map key is not required for the maps to appear, but will display a popup reminder every time a map view is displayed.

Using the Map View

Once you have obtained a key, the next step is to add the map view to your app. You can obtain the control from the 3.

Once you have added the assembly to your app, you then need to add your Bing Maps Key to the Info.plist file in your project. This is a string key-value item with the key, BingMapsKey, with the value being the key you obtained from the portal.

Now, all that is left to do is to add the map view to your view. This can be done in the code or in the designers (storyboards or interface files).

If we are going to use the designers, then you can just add a UIView to your app, and then change the Class to be BMMapView. When the app is launched, a map should appear. If the code is used, then this snippet is all that is needed:

    // create the map view
    BMMapView mapView = new BMMapView (UIScreen.MainScreen.Bounds);
    // add to some View
    View.AddSubview (mapView);
    // show the user's location on the map (optional)
    mapView.ShowsUserLocation = true;

Binding Flurry Analytics with Xamarin

Over the last few days I have been creating a Xamarin.iOS and Xamarin.Android binding for Flurry Analytics SDKs. Flurry Analytics is a neat little library that allows for tracking of your app usage and various user stats.
And, because I really enjoyed my time doing this, I thought that I will share just a bit of the fun times and the not-so-fun times.

I have split this project up into four parts:

  1. Introduction and Pre-requisites
  2. Xamarin.iOS binding
  3. Xamarin.Android binding
  4. Flurry.Analytics in the wild

Pre-requisites

Before we start any coding, we need to get all our tools and files needed for the actual binding tasks.

I was using both Windows 8, with Visual Studio, and Mac OS X with Xamarin Studio. You can bind the Android library on either platform, but for iOS, it is easier to use the Mac. In this case, I will use the Mac for both iOS and Android.

Some of my very excellent sources for this process were the actual Xamarin documentation:

But, along with this info, there are just a few things that you may need. Firstly, it is good to update your Xamarin Studio to the latest stable release as this makes sure that all potential problems are minimized from the start.

Then, the next cool tool to get hold of is, Objective Sharpie, for binding iOS libraries. This tool is quite nifty for quickly generating the .NET interfaces from the Objective-C header files. It uses the header files not the actual compiled native library. The Xamarin docs on Objective Sharpie has a brief walkthrough on how to use the tool, so I will assume you know how to use it.

After we have all our tools ready to go, we need those SDKs from Flurry.

What I did was to create a free account by Flurry, and then created an Android and iOS Application using their dashboard. You have to have an application before they will let you download their SDKs. This step is pretty straight forward, so I won’t go into it here right now. Once you have created and downloaded the SDKs, you can extract them to a nice location for reference.

Now to get started with the real work…

But, as this is quite a long process, I will be splitting this article into this intro, the iOS binding and the Android binding. So, enjoy!

Binding Flurry Analytics with Xamarin.iOS

This week, I needed to use Flurry Analytics in my Xamarin.iOS and my Xamarin.Android apps, but there was no .NET SDK for these platforms. Flurry did provide a SDK for each platform, but it was the native libraries, which cannot be used directly in .NET. Given this, I decided to bind those native libraries using Xamarin.iOS and Xamarin.Android.

I have split this project up into four parts:

  1. Introduction and Pre-requisites
  2. Xamarin.iOS binding
  3. Xamarin.Android binding
  4. Flurry.Analytics in the wild

The first thing we are going to bind is the iOS SDK for Flurry Analytics. This task needs two things from the downloaded SDK:

  • [iOS-sdk-path]/Flurry-iOS-5.2.0/Flurry/Flurry.h
  • [OS-sdk-path]/Flurry-iOS-5.2.0/Flurry/libFlurry_5.2.0.a

Of course, the version numbers may change for later releases. The header file (.h) is going to be used to generate the .NET interfaces and enums. The library (.a) is going to be used in the project.

Just before we do the cool things, we should just create our C# solution for the Xamarin.iOS binding. Just create a new project/solution and select the “iOS Binding Project” template. For my project name I used Flurry.Analytics.iOS, but you could use anything.

The default project has a few files in it, we will have a look at each one in depth as we go through the binding steps:

  • ApiDefinition.cs (this is for the generated .NET interfaces from Objective Sharpie)
  • AssemblyInfo.cs (the usual assembly information attributes)
  • Extras.cs (this is for any additional changes that are needed for the bound types)
  • StructsAndEnums.cs (this is for any extra types that you wish to include in the final assembly)

Creating the Initial Binding

This step will involve using Objective Sharpie to get us started, but we will almost always have to go in and tweak a few things.

So, first things first, open Objective Sharpie and start the wizard. Select the Flurry.h header file, and I used the namespace Flurry.Analytics. After saving your file somewhere, we can start the tweaks. I don’t save the result into my solution as the amount of tweaks needed is high enough that I like to have a before and after file. Also, this library is very small. But, there is no reason why you couldn’t just save over the ApiDefinition.cs file in you project.

Before we start, here is a snippet from the Flurry.h file:

    // an enum
    typedef enum {
        FlurryLogLevelNone = 0,
        FlurryLogLevelCriticalOnly,
        FlurryLogLevelDebug,
        FlurryLogLevelAll
    } FlurryLogLevel;

    // the main class
    @interface Flurry : NSObject {
    }
    // ...
    + (void)setAppVersion:(NSString *)version;
    + (NSString *)getFlurryAgentVersion;
    + (void)startSession:(NSString *)apiKey;
    // ...
    @end

In this snippet of Objective-C goodness, there is an enum named FlurryLogLevel and an @interface named Flurry. This will translate into a C# enum and class respectively. It is also good to note that all the methods on this particular type will be static.

The generated C# for this snippet is:

    // the enum
    public enum FlurryLogLevel : [unmapped: unexposed: Elaborated] {
        None = 0,
        CriticalOnly,
        Debug,
        All
    }

    // the main class
    [BaseType (typeof (NSObject))]
    public partial interface Flurry {
        // ...
        [Static, Export ("appVersion")]
        [Verify ("ObjC method massaged into setter property", "Flurry/Flurry.h", Line = 61)]
        string AppVersion { set; }

        [Static, Export ("getFlurryAgentVersion")]
        [Verify ("ObjC method massaged into getter property", "Flurry/Flurry.h", Line = 80)]
        string GetFlurryAgentVersion { get; }

        [Static, Export ("startSession:")]
        void StartSession (string apiKey);
        // ...
    }

As you can see, the generated file is similar, but not quite the same.

Tweaking the Generated Binding (ApiDefinition.cs)

After we generated our C# files, there are several things to note:

  • This code doesn’t compile at all
  • A good few of our methods are set to properties, especially note the setter-only ones
  • There is a [Verify(...)] attribute which doesn’t even exist
  • Some [Export] attributes have a value that ends in a colon (:), and others do not
  • The type is not a class but an interface
  • The base type is specified as a [BaseType] attribute
  • The enum has the item prefixes removed

This is quite a list here, but not all are bad. Objective Sharpie can’t generate perfect code as Objective-C is not C#, so there will always be human intervention. In these cases, it will generate what it thinks to be best and then let you know.

The [Verify] attribute is one way that Objective Sharpie lets you know that it changed something. In this instance, it is letting us know that it changed the setAppVersion method into a property. This is because usually there is both a getter and a setter method for a Objective-C property. But in this case, it is actually a method.

As you may note, these properties have a slightly different [Export] attribute in that these members don’t end in a colon. This is valid for Objective-C property setters, but as this is a method, we should add a colon back. It is also valid for methods that take parameters.

So, to fix those properties, remove the [Verify] attribute. We should also fix the exported name, and for the setter property, add the colon back in.

    [BaseType (typeof (NSObject), Name = "Flurry")]
    public partial interface FlurryAgent {

        [Static, Export ("setAppVersion:")]
        void SetAppVersion (string version);

        [Static, Export ("getFlurryAgentVersion")]
        string GetFlurryAgentVersion ();

        [Static, Export ("startSession:")]
        void StartSession (string apiKey);

    }

As you can see here, I corrected the SetAppVersion write-only property by transforming it into a void method that exports to setAppVersion:. Note the full name and colon as this method takes a parameter.

Also, I needed to fix the GetFlurryAgentVersion read-only property by transforming it into a string method that exports to “getFlurryAgentVersion”. Note the lack of colon as this method takes no parameters.

And finally, I decided I wanted my final class to be called FlurryAgent to avoid confusion with the namespace as well as indicate that it was the Agent. In order to do this, All I needed to do was to rename the type and add the Name property in the attribute with a value of "Flurry".

The reason for adding this property is, after changing the type name, the binding no longer will be able to find the underlying Objective-C type. Usually the binding will use the default name if none is provided, the C# class name. After changing it, it no longer reflected the Objective-C type name, hence the new property.

Managing the Structures and Enumerations (StructsAndEnums.cs)

The last thing to fix, in the generated code, is the enum.

This is simple to do, just remove the strange attribute-like bit: [unmapped: unexposed: Elaborated].

One of the nice things of Objective Sharpie is that it removes the enum prefixes and just uses the relevant bits. For example the original item was FlurryLogLevelNone, but this is now just None.

The final enum looks like this after it has been moved to StructsAndEnums.cs:

    public enum FlurryLogLevel {
        None = 0,
        CriticalOnly,
        Debug,
        All
    }

So far, all our generated code has been moved into either, ApiDefinitions.cs (the class), or StructsAndEnums.cs (the enum). We are still not complete yet, but we are nearly there.

Additional Binding Code (Extras.cs)

Once we have the basic binding done, we can always extend what the native library provided with extra features. This is done by adding new source files to the project.

For example, if for some reason you need to add any extra bindings to the library, say to access a private member or even just to make the new API cleaner and more .NET-like.

For example you could do something like this:

    public partial class FlurryAgent : NSObject {
        public static void LogTimedEvent (string eventName, Action action) {
            try {
                FlurryAgent.LogEvent (eventName, true);
                action(); // do our task
            } catch {
                FlurryAgent.EndTimedEvent (eventName, null);
                throw;
            }
        }
    }

What is happening here is that I am adding a new method to the final .NET assembly. This method can make use of the methods in the API definition interface. In this particular example, I am adding a method that can take an Actionand make sure that we fire a ‘starting’ and then an ‘ending’ event to the Flurry servers. This is a new bit of functionality that can be baked right into the binding assembly.

Working with the Native Library (.a)

This step is actually quite easy, just add the native library (.a) to the project, and you are almost done.

When you add the native library to the project, Xamarin Studio will create a new .linkwith.cs file that matches the library name. For example with Flurry, I added the libFlurry_5.2.0.a library to my project and it generated a libFlurry_5.2.0.linkwith.cs.

This new file is the last bit that needs to be done. It is for the linker when using this library in an iOS app. It usually will contain only a single line, a single [LinkWith] attribute. This attribute allows you to specify what targets this library can be used with, what extra iOS frameworks are needed, etc:

    [assembly: LinkWith (
        "libFlurry_5.2.0.a", 
        LinkTarget.Simulator | LinkTarget.ArmV6 | LinkTarget.ArmV7 | LinkTarget.ArmV7s, 
        ForceLoad = true)]

This is the default line, split for readability, and usually it is all that is needed. What this does is say that the linker must use the libFlurry_5.2.0.a native library as well as this library can be used on the Simulator and all the devices. The ForceLoad is used by the Xamarin.iOS linker, and specifies a linker flag. According to the documentation, this should always be true for now.

But just before we finish up, we need to add any required frameworks. Without this, there will be strange happenings…but different depending the configuration.

If building for Debug, the app will launch, but nothing will happen at all. The methods can be called and everything, but no actions will take place.

If building for Release, the linker will throw an error:

Error MT5211: Native linking failed, undefined Objective-C class: _OBJC_CLASS_\$_Flurry.
If ‘_OBJC_CLASS_\$_Flurry’ is a protocol from a third-party binding, please check that it has the [Protocol] attribute in its api definition file, otherwise verify that all the necessary frameworks have been referenced and native libraries are properly linked in.

This error is giving us lots of information for @protocol, but as we don’t have any in our project (recall the @interface), we can safely ignore the first part of the second message and focus on the ‘frameworks’ part. According to the Flurry documentation, the Security.framework is required and the SystemConfiguration.framework is optional, but recommended.

So, I added both frameworks, using the Frameworksproperty:

    [assembly: LinkWith (
        "libFlurry_5.2.0.a",
        LinkTarget.Simulator | LinkTarget.ArmV6 | LinkTarget.ArmV7 | LinkTarget.ArmV7s, 
        ForceLoad = true, 
        Frameworks = "SystemConfiguration Security")]

As shown here, the frameworks are space-separated and do not have the .framework extensions.

Finishing Up

So far we have defined our API, added any additional logic or types, added the native library, set up the linker and specified the frameworks. This is all that is needed, so our library should build fine now and we should be able to use it in an Xamarin.iOS app:

    // the methods
    FlurryAgent.StartSession("PQSZJRK4B5BW8Q7YQQXF");

    // the properties that we changed back into methods
    FlurryAgent.GetFlurryAgentVersion ();

    // the extra method that we added
    FlurryAgent.LogTimedEvent ("started", () => {
        // ...
    });

Under the Hood

One thing to know is that the iOS binding project is actually not really compiled directly, but are a series of stages. Each file actually has a different build action.

The first stage to building the binding is to build a reference assembly with the files:

  • ApiDefinition.cs (with build action ObjcBindingApiDefinition)
  • StructsAndEnums.cs (with build action ObjcBindingCoreSource)

Next, this assembly is then reflected to generate the actual binding code. This generated code is then compiled with the rest of the files:

  • Extras.cs (with build action Compile)
  • StructsAndEnums.cs (again with build action ObjcBindingCoreSource)
  • libFlurry_5.2.0.linkwith.cs (with build action Compile)
  • libFlurry_5.2.0.a (with build action ObjcBindingNativeLibrary but is actually just a resource)

An example of the generated code in the second stage, the interface member void StartSession(string) is actually used to generate a big block of code:

    [Export ("startSession:")]
    [CompilerGenerated]
    public static void StartSession (string apiKey)
    {
        if (apiKey == null)
            throw new ArgumentNullException ("apiKey");
        var nsapiKey = NSString.CreateNative (apiKey);

        MonoTouch.ObjCRuntime.Messaging.void_objc_msgSend_IntPtr (class_ptr, selStartSession_Handle, nsapiKey);
        NSString.ReleaseNative (nsapiKey);
    }

This is all the magic that happens under the hood to redirect values from the managed .NET to the underlying native library.