Xamarin Bindings for Complex Android Libraries using SBA

Taras Shevchuk
7 min readAug 23, 2021

Xamarin development can be frustrating at times. Despite the fact that it is C# development, from time to time you still have to struggle with some native code. You might find some native library which does exactly what you need for your application, but it is not available in NuGet yet.

Sure, developers have created bindings library projects which make it easy to create C# wrappers. In some cases it is quite enough to just add native library to such a project, run a build, and that is it. You have a dll with all C# classes you need.

But sometimes, native libraries are not so simple. They can cause warnings and errors (sometimes hundreds of them) during the wrappers creation and you have to manually fix it by adding some metadata info to this project. So, how do we work around such problems and not spend several weeks on it?

My experience with ExoPlayer libraries

You might have heard about ExoPlayer. This is an application level media player for Android. It has lots of features, good performance and it is still maintained by Google (at least at the moment this article was written, we all know how Google loves to bury its own projects).

This player has several bindings libraries for Xamarin. The most popular one is developed by Baseflow. Nevertheless, lots of them are not maintained and even Baseflow has not been updated theirs for a year. As a result, newer versions of ExoPlayer with all of the new features, bugfixes and improvements were not available in Xamarin.

At iFIT however, we wanted to have these new features, ideally with minimal pain and time spent. I was tasked with creating our own bindings library for the latest version of the ExoPlayer (at that moment it was version 2.13.3). Results of this task can be found in this public repository: iFIT.Xamarin.ExoPlayer.

This library was a tough guy. For example, here is what I got when I tried to build bindings project for all main ExoPlayer libraries without any metadata or addition files:

It was obvious that I needed some strategy in order to complete this task and, what is more important, make this project maintainable in the future. That is how I came up with the creation of the Sufficient Bindings Approach (SBA).

Sufficient Bindings Approach (SBA)

Default tools for the the bindings projects creation do not always work as expected, they are not perfect. However, it is quite possible to handle different circumstances which may happen, if we will use some strict principles and apply some recommendations described below.

SBA Principles

I. Zero-tolerance to warnings (main principle)

Warnings in Xamarin.Android bindings library projects always indicate that something went wrong during the bindings generation. These warnings may result in missing API or, what is worse, compile-time and run-time errors. That is why it is essential to avoid warnings in bindings library projects.

II. Create one fat AAR instead of using several small AAR-s

If you have several libraries to bind and you are sure that you will not use them separately, then create an Android Studio project where you will be able to merge all of them into one fat AAR library. The problem with several small AAR-s is that we have to create bindings library projects for each of them. And, what is worse, if it has dependencies on other bindings library projects, the bindings generator struggles with inheritance and generics.

III. Expose only classses and interfaces you need (at least at the beginning)

If you try to bind some complex native library, you are probably going to have dozens (if not hundreds) of warnings and errors during the build. Such result may be very frustrating. Also, fixing these errors and warnings may be useless if we do not use this API at all. That is why try to apply these rules for Metadata.xml files:

  • Remove all package nodes except ones we really use in C# code.
  • Remove all class and interface nodes except ones we really use in C# code.

IV. Avoid removing nodes from classes or interfaces

In contrast with package, class,or interface members, it is better to avoid excluding such members as field, method, or constructor.

First of all, it may be a little overhead, especially if exposing all the API of the class produces zero warnings. In some cases you need to add only one additional class or interface to have no warnings.

Secondly, in some cases you can create build errors in projects which depend on this bindings library: sometimes they produce implementor java classes during the build and if you have removed some nodes from interface or abstract class, implementor class may not compile then.

That is why it is better to avoid removing nodes from class-es and interface-s. However, it is still allowed since there should always be zero warnings in the bindings library project.

V. Add notes about missing members or explanations if you have removed nodes from classes or interfaces

In cases where you have added a remove node, it is mandatory to add a comment: add there all missing members (class-es and interface-s), due to which you have added this remove node. In the future you might add these missing members and then we will know that such remove nodes can be deleted without any struggles. In case if adding of remove node is caused by some other reason (for example, implemented interface already has such method), then add a comment explanating why it is being removed.

VI. Create separate metadata file for each class or interface with some changes and/or removed nodes

The folders structure in Transofrms folder should be the same as in the native library. The name of any metadata file should be Metadata.${JavaClassName}.xml for class-es and Metadata.${JavaInterfaceName}.xml for interface-s.

VII. Always check that a sample project for your bindings library is built successfully

It is possible that your bindings library has no warnings and errors during the build, but a sample project which is using this library, does not compile because of some weird java errors. It can happen because java-wrappers for C# classes are created in Xamarin.Android project during the build, not in bindings project. That is why it is quite important to have a sample project and check if it is compiling from time to time.

SBA recommendations (not mandatory for usage, but still a good practice)

1. There is no problem in creating some custom Java/Kotlin wrappers. You can easily add it into the Android Studio project and it will be exposed to the bindings project.

2. Check the dependency tree in the Android Studio project or Maven Repository for the native library in order to know what NuGet packages you should add to the bindings project and what the dependencies should be for your NuGet created from bindings project. Otherwise you might have some run-time exceptions like ClassNotFoundException or similar.

3. Remove events creation for listeners. It is easier to use the native Android approach with listeners rather than to create events with EventArgs and additional C# logic generation, especially with some inheritance and overlapping of names. In order to remove events generation in general, you can use the following nodes:

<!--Removing all event names from java listeners in order -->
<!--to prevent EventHandlers and EventArgs classes creation-->
<attr
path="/api/package/*[
substring(@name, string-length(@name) -
string-length('Listener') + 1,
string-length('Listener')) = 'Listener']/method"
name="eventName" />
<attr
path="/api/package/*/method[
@name='addListener' or
@name='removeListener' or
@name='setListener']"
name="eventName" />

4. Place all the items in the Metadata files in alphabetical order. There are two main reasons for it:

  • It provides easier search for any item inside the file.
  • It reduces the amount of merge conflicts in case of any parallel development.

5. Create enums. If you see that some parameter/value looks like an enum – then create the related enum:

  • Use the EnumFields.xml file to create enums (NOTE: only one EnumFields.xml file can exist).
  • Use the EnumMethods.xml file to replace int or long values with the brand-new enums in the methods where they are used (NOTE: only one EnumMethods.xml file can exist). As an alternative, you can get rid of theEnumMethods.xml file and use attr nodes in the relevant Metadata.*.xml files. In this case, you will also be able to change the java fields, so they will be exposed as enum properties in C#. You can find an example of how these attributes look like in theMetadata.BaseRenderer.xml file in the iFIT.Xamarin.ExoPlayer bindings library code.

Conclusion

The Sufficient Bindings Approach has both pros and cons.

On the positive side, this approach makes bindings library projects maintainable. Other people will be able to understand why it works because such projects will be documented with explanation comments. Their files structure will be clear and straightforward. And, they will know how to add some new API and functionality without breaking this structure.

On the other hand, libraries built with such an approach will not expose all ot the native API, at least at the beginning of the development process.

I believe, that the SBA is just an instrument which may help you to create a bindings projects for some complex libraries like ExoPlayer. In the long-term, all of the native API can be exposed to the C# code, since all of these errors and warnings can be fixed with Additions or Metadata files. It is just a question of time. But in between, it is not a big problem to hide some of the native API, especially if we do not use it in C#.

--

--