Java bindings

In the same manner as iOS, Xamarin has provided full support for calling into Java libraries from C# with Xamarin.Android. The native Android SDKs function in this way and developers can leverage the Android Java Bindings project to take advantage of other native Java libraries in C#. The main difference here is that not a lot has to be done by hand in comparison to Objective-C bindings. The Java syntax is very similar to that of C#, so many mappings are exactly one-to-one. In addition, Java has metadata information included with its libraries, which Xamarin uses to automatically generate the C# code required for calling into Java.

As an example, let's make a binding for the Android version of the Google Analytics SDK. Before we begin, download the SDK from http://tinyurl.com/GoogleAnalyticsForAndroid. At the time of writing, the version of the Android SDK 3.01, so some of these instructions might change over time.

Let's begin creating a Java binding as follows:

  1. Start a new Android Java Bindings Library project in Xamarin Studio. You can use the same solution as we did for iOS if you wish.
  2. Name the project GoogleAnalytics.Droid.
  3. Add libGoogleAnalyticsServices.jar from the Android SDK to the project under the Jars folder. By default, the build action for the file will be EmbeddedJar. This packs the jar file into the DLL, which is the best option for ease of use.
  4. Build the project. You will get a few errors, which we'll address in a moment.

Most of the time you spend working on Java bindings will be to fix small issues that prevent the generated C# code from compiling. Don't fret; a lot of libraries will work on the first try without having to make any changes at all. Generally, the larger the Java library is, the more work you have to do to get it working from C#.

The following are the types of issues you might run into:

  • Java obfuscation: If the library is run through an obfuscation tool such as ProGuard, the class and method names might not be valid C# names.
  • Covariant return types: Java has different rules for return types in overridden virtual methods than C# does. For this reason, you might need to modify the return type for the generated C# code to compile.
  • Visibility: The rules that Java has for accessibility are different from those of C#; the visibility of methods in subclasses can be changed. Sometimes, you will have to change visibility in C# to get it to compile.
  • Naming collisions: Sometimes, the C# code generator can get things a bit wrong and generate two members or classes with the same name.
  • Java generics: The use of generic classes in Java can often cause issues in C#.

So before we get started on solving these issues in our Java binding, let's first clean up the namespaces in the project. Java namespaces are of the form com.mycompany.mylibrary by default, so let's change the definition to match C# more closely. In the Transforms directory of the project, open Metadata.xml and add the following XML tag inside the root metadata node:

<attr path="/api/package[@name='com.google.analytics.tracking
  .android']" name="managedName">GoogleAnalytics.Tracking</attr>

The attr node tells the Xamarin compiler what needs to be replaced in the Java definition with another value. In this case, we are replacing managedName of the package with GoogleAnalytics.Tracking because it will make much more sense in C#. The path value might look a bit strange, which is because it uses an XML matching query language named XPath. In general, just think of it as a pattern matching query for XML. For full documentation on XPath syntax, check out some of the many resources online such as http://w3schools.com/xpath.

You might be asking yourself at this point, what is the XPath expression matching against? Return to Xamarin Studio and right-click on the solution at the top. Navigate to Display Options | Show All Files. Open api.xml under the obj/Debug folder. This is the Java definition file that describes all the types and methods within the Java library. If you notice, the XML here directly correlates to the XPath expressions we'll be writing.

In our next step, let's remove all the packages (or namespaces) we don't plan on using in this library. This is generally a good idea for large libraries since you don't want to waste time fixing issues with parts of the library you won't even be calling from C#. Note that it doesn't actually remove the Java code; it just prevents the generation of any C# declarations for calling it from C#.

Add the following declarations in Metadata.xml:

<remove-node
  path="/api/package[@name='com.google.analytics
  .containertag.common']" />
<remove-node
  path="/api/package[@name='com.google.analytics
  .containertag.proto']" />
<remove-node
  path="/api/package[@name='com.google.analytics
  .midtier.proto.containertag']" />
<remove-node
  path="/api/package[@name='com.google.android
  .gms.analytics.internal']" />
<remove-node
  path="/api/package[@name='com.google.android
  .gms.common.util']" />
<remove-nodepath="/api/package[@name='com.google.tagmanager']" />
<remove-node
  path="/api/package[@name='com.google.tagmanager.proto']" />
<remove-node
  path="/api/package[@name='com.google.tagmanager.protobuf.nano']" />

Now when you build the library, we can start resolving issues. The first error you will receive will be something like the following:

GoogleAnalytics.Tracking.GoogleAnalytics.cs(74,74): 
    Error CS0234: The type or namespace name 'TrackerHandler' 
    does not exist in the namespace 'GoogleAnalytics.Tracking'. 
    Are you missing an assembly reference?

If we locate TrackerHandler within the api.xml file, we'll see the following class declaration:

<class
  abstract="true" deprecated="not deprecated"
  extends="java.lang.Object"
  extends-generic-aware="java.lang.Object"
  final="false" name="TrackerHandler"
  static="false" visibility=""/>

So, can you spot the problem? We need to fill out the visibility XML attribute, which for some reason is blank. Add the following line to Metadata.xml:

<attr
  path="/api/package[@name='com.google.analytics
  .tracking.android']/class[@name='TrackerHandler']"
  name="visibility">public</attr>

This XPath expression will locate the TrackerHandler class inside the com.google.analytics.tracking.android package and change visibility to public.

If you build the project now, it will complete successfully with one warning. In Java binding projects, it is a good idea to fix warnings since they generally indicate that a class or method is being omitted from the binding. Notice the following warning:

GoogleAnalytics.Droid: Warning BG8102: 
    Class GoogleAnalytics.Tracking.CampaignTrackingService has   
    unknown base type android.app.IntentService (BG8102)  
    (GoogleAnalytics.Droid)

To fix this issue, locate the type definition for CampaignTrackingService in api.xml, which is as follows:

<class
  abstract="false" deprecated="not deprecated"
  extends="android.app.IntentService"
  extends-generic-aware="android.app.IntentService"
  final="false" name="CampaignTrackingService"
  static="false" visibility="public">

The way to fix the issue here is to change the base class to the Xamarin.Android definition for IntentService. Add the following code to Metadata.xml:

<attr
  path="/api/package[@name='com.google.analytics
  .tracking.android']/class[@name='CampaignTrackingService']"
  name="extends">mono.android.app.IntentService</attr>

This changes the extends attribute to use the IntentService found in Mono.Android.dll. I located the Java name for this class by opening Mono.Android.dll in Xamarin Studio's Assembly Browser. Let's take a look at the Register attribute, as shown in the following screenshot:

Java bindings

To inspect the *.dll files in Xamarin Studio, you merely have to open them. You can also double-click on any assembly in the References folder in your project.

If you build the binding project now, we're left with one last error, which is as follows:

GoogleAnalytics.Tracking.CampaignTrackingService.cs(24,24): 
    Error CS0507: 
    'CampaignTrackingService.OnHandleIntent(Intent)': 
    cannot change access modifiers when overriding 'protected'    
    inherited member 
    'IntentService.OnHandleIntent(Android.Content.Intent)' 
    (CS0507) (GoogleAnalytics.Droid)

If you navigate to the api.xml file, you can see the definition for OnHandleIntent as follows:

<method
  abstract="false" deprecated="not deprecated" final="false"
  name="onHandleIntent" native="false" return="void"
  static="false" synchronized="false" visibility="public">

We can see here that the Java method for this class is public, but the base class is protected. So, the best way to fix this is to change the C# version to protected as well. Writing an XPath expression to match this is a bit more complicated, but luckily Xamarin has an easy way to retrieve it. If you double-click on the error message in the Errors pad of Xamarin Studio, you'll see the following comment in the generated C# code:

// Metadata.xml XPath method reference:
  path="/api/package[@name='com.google.analytics
  .tracking.android']/class[@name='CampaignTrackingService']
  /method[@name='onHandleIntent' and count(parameter)=1 and
  parameter[1][@type='android.content.Intent']]"

Copy this value to path and add the following to Metadata.xml:

<attr path="/api/package[@name='com.google.analytics
  .tracking.android']/class[@name='CampaignTrackingService']
  /method[@name='onHandleIntent' and count(parameter)=1 and
  parameter[1][@type='android.content.Intent']]"
  name="visibility">protected</attr>

Now, we can build the project and get zero errors and zero warnings. The library is now ready for use within your Xamarin.Android projects.

However, if you start working with the library, notice how the parameter names for the methods are p0, p1, p2, and so on. Here are a few method definitions of the EasyTracker class:

public static EasyTracker GetInstance(Context p0);
public static void SetResourcePackageName(string p0);
public virtual void ActivityStart(Activity p0);
public virtual void ActivityStop(Activity p0);

You can imagine how difficult it would be to consume a Java library without knowing the proper parameter names. The reason the parameters are named this way is because the Java metadata for its libraries does not include the information to set the correct name for each parameter. So, Xamarin.Android does the best thing it can and autonames each parameter sequentially.

To rename the parameters in this class, we can add the following to Metadata.xml:

<attr path="/api/package[@name='com.google.analytics
  .tracking.android']/class[@name='EasyTracker']
  /method[@name='getInstance']/parameter[@name='p0']"
  name="name">context</attr>
<attr path="/api/package[@name='com.google.analytics
  .tracking.android']/class[@name='EasyTracker']
  /method[@name='setResourcePackageName']/parameter[@name='p0']"
  name="name">packageName</attr>
<attr path="/api/package[@name='com.google.analytics
  .tracking.android']/class[@name='EasyTracker']
  /method[@name='activityStart']/parameter[@name='p0']"
  name="name">activity</attr>
<attr path="/api/package[@name='com.google.analytics
  .tracking.android']/class[@name='EasyTracker']
  /method[@name='activityStop']/parameter[@name='p0']"
  name="name">activity</attr>

On rebuilding the binding project, this will effectively rename the parameters for these four methods in the EasyTracker class. At this time, I would recommend that you go through the classes you plan on using in your application and rename the parameters so that it will make more sense to you. You might need to refer to the Google Analytics documentation to get the naming correct. Luckily, there is a javadocs.zip file included in the SDK that provides HTML reference for the library.

For a full reference on implementing Java bindings, make sure you check out Xamarin's documentation site at http://docs.xamarin.com/android. There are certainly more complicated scenarios than what we ran into when creating a binding for the Google Analytics library.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.235.23