Binding Android Library for Xamarin is A Disaster

.NET developers like Xamarin because they can develop Android and iOS apps in C#. However, if you are not afraid of learning new programming languages, you’d better choose Kotlin for Android and Swift for iOS. Unlike React Native, Flutter and Cordova, with Xamarin, you cannot create one codebase for both platforms in Visual Studio. Moreover, when you try to bind a complicated and obfuscated Android library, you may be in big trouble.  I just suffered from the pain of binding Dynamsoft Camera SDK for Android.

Updating Xamarin in Visual Studio

If your Xamarin version is old, you can check and install the latest version by selecting Tools > Options > Xamarin > Other.

Xamarin update

After updating Xamarin to the latest version, I was frustrated when seeing the following alert message box.

Xamarin error

It is a bad user experience if you cannot use your tools normally after updating it via official installer.

Luckily, I don’t need to remove everything and install Xamarin again. According to the StackOverflow solution, I just need to clear all files in C:\Users\%username%\AppData\Local\Microsoft\VisualStudio\14.0\ComponentModelCache and re-launch Visual Studio. The corresponding version number for Visual Studio 2015 is 14.0.

Metadata for Binding Android Library

Basically, if you want to bind an Android library(.aar), you just need to create an Android Class Library project.  Then drag the AAR file to Jars folder and change the build action to LibraryProjectZip.

Dynamsoft Camera SDK is complicated and obfuscated. It’s a nightmare that when I tried to build it, I got countless errors.

xamarin missing assembly reference

To clear the errors, you have to open Transforms/Metadata.xml to add metadata. In the meantime, Java Decompiler can assist you in fixing issues.

java decompiler

Error: are you missing an assembly reference

Some classes and interfaces do not have the proper visibility. To solve the issue, set visibility public:

<attr path="/api/package[@name='com.bumptech.glide']/interface[@name='BitmapOptions']"
        name="visibility">public</attr>

  <attr path="/api/package[@name='com.bumptech.glide']/interface[@name='DrawableOptions']"
        name="visibility">public</attr>

  <attr path="/api/package[@name='com.bumptech.glide']/interface[@name='DownloadOptions']"
        name="visibility">public</attr>

  <attr path="/api/package[@name='com.bumptech.glide.load.engine.bitmap_recycle']/interface[@name='LruPoolStrategy']"
        name="visibility">public</attr>

  <attr path="/api/package[@name='com.bumptech.glide.load.engine.bitmap_recycle']/interface[@name='Poolable']"
        name="visibility">public</attr>
  
  <attr path="/api/package[@name='com.bumptech.glide.load.engine']/interface[@name='EngineJobListener']"
        name="visibility">public</attr>
  
  <attr path="/api/package[@name='com.bumptech.glide.load.engine.bitmap_recycle']/class[@name='BaseKeyPool']"
        name="visibility">public</attr>

  <attr path="/api/package[@name='com.bumptech.glide.load.engine']/class[@name='EngineJob']"
        name="visibility">public</attr>

  <attr path="/api/package[@name='com.bumptech.glide.load.engine']/class[@name='EngineResource']"
        name="visibility">public</attr>

  <attr path="/api/package[@name='org.litepal.crud']/class[@name='AssociationsAnalyzer']"
        name="visibility">public</attr>

  <attr path="/api/package[@name='org.litepal.tablemanager']/class[@name='Creator']"
        name="visibility">public</attr>

  <attr path="/api/package[@name='org.litepal.crud']/class[@name='DataHandler']"
        name="visibility">public</attr>

Error: already contains a definition for ‘p0’ or ‘p1’

If you see this kind of error, change a parameter name from the generic p0 or p1 to a more meaningful one.

<attr path="/api/package[@name='com.dynamsoft.camerasdk.view']/interface[@name='DcsImageEditorViewListener']/method[@name='onCancelTapped']/parameter[@name='p0']"
        name="name">paramDcsImageEditorView</attr>

  <attr path="/api/package[@name='com.dynamsoft.camerasdk.view']/interface[@name='DcsImageEditorViewListener']/method[@name='onOkTapped']/parameter[@name='p0']"
        name="name">paramDcsImageEditorView</attr>

  <attr path="/api/package[@name='com.dynamsoft.camerasdk.view']/interface[@name='DcsImageEditorViewListener']/method[@name='onOkTapped']/parameter[@name='p1']"
        name="name">paramDcsException</attr>

Error: it does not have the matching return type

According to the error message, we just need to change the return type.

<attr path="/api/package[@name='com.bumptech.glide']/
    class[@name='BitmapRequestBuilder']/
    method[@name='fitCenter']"
    name="managedReturn">GenericRequestBuilder</attr>

  <attr path="/api/package[@name='com.bumptech.glide']/
    class[@name='BitmapRequestBuilder']/
    method[@name='centerCrop']"
    name="managedReturn">GenericRequestBuilder</attr>

  <attr path="/api/package[@name='com.bumptech.glide']/
    class[@name='DrawableRequestBuilder']/
    method[@name='crossFade']"
    name="managedReturn">GenericRequestBuilder</attr>

  <attr path="/api/package[@name='com.bumptech.glide']/
    class[@name='DrawableRequestBuilder']/
    method[@name='fitCenter']"
    name="managedReturn">GenericRequestBuilder</attr>

  <attr path="/api/package[@name='com.bumptech.glide']/
    class[@name='DrawableRequestBuilder']/
    method[@name='crossFade']"
    name="managedReturn">GenericRequestBuilder</attr>

  <attr path="/api/package[@name='com.bumptech.glide']/
    class[@name='DrawableRequestBuilder']/
    method[@name='centerCrop']"
    name="managedReturn">GenericRequestBuilder</attr>

  <attr path="/api/package[@name='com.bumptech.glide']/
    class[@name='GifRequestBuilder']/
    method[@name='fitCenter']"
    name="managedReturn">GenericRequestBuilder</attr>

  <attr path="/api/package[@name='com.bumptech.glide']/
    class[@name='GifRequestBuilder']/
    method[@name='centerCrop']"
    name="managedReturn">GenericRequestBuilder</attr>

  <attr path="/api/package[@name='com.bumptech.glide']/
    class[@name='GifRequestBuilder']/
    method[@name='crossFade']"
    name="managedReturn">GenericRequestBuilder</attr>

Error: member names cannot be the same as their enclosing type

If the parameter name is same as the type name, change it.

<attr path="/api/package[@name='com.bumptech.glide']/class[@name='Priority']/field[@name='priority']"
        name="managedName">mPriority</attr>

Error: does not implement interface member

The issue seems to be caused by abstract methods. My workaround is to remove them.

<remove-node path="/api/package[@name='com.bumptech.glide']/class[@name='ListPreloader.PreloadTarget']/method[@name='onResourceReady']" />
  
  <remove-node path="/api/package[@name='com.bumptech.glide.load.data']/class[@name='LocalUriFetcher']/method[@name='close']" />

  <remove-node path="/api/package[@name='com.bumptech.glide.load.data']/class[@name='LocalUriFetcher']/method[@name='loadResource']" />

  <remove-node path="/api/package[@name='com.bumptech.glide.load.data']/class[@name='AssetPathFetcher']/method[@name='close']" />

  <remove-node path="/api/package[@name='com.bumptech.glide.load.data']/class[@name='AssetPathFetcher']/method[@name='loadResource']" />

  <remove-node path="/api/package[@name='com.bumptech.glide.load.engine.cache']/interface[@name='MemoryCache']/method[@name='put']" />

  <remove-node path="/api/package[@name='com.bumptech.glide.load.engine.cache']/interface[@name='MemoryCache']/method[@name='remove']" />

  <remove-node path="/api/package[@name='com.bumptech.glide.load.model']/interface[@name='ModelLoader']/method[@name='getResourceFetcher']" />

  <remove-node path="/api/package[@name='com.bumptech.glide.load']/interface[@name='Encoder']/method[@name='encode']" />

  <remove-node path="/api/package[@name='com.bumptech.glide.load']/interface[@name='ResourceDecoder']/method[@name='decode']" />

  <remove-node path="/api/package[@name='com.bumptech.glide.request.target']/interface[@name='Target']/method[@name='onResourceReady']" />

  <remove-node path="/api/package[@name='com.bumptech.glide.request.target']/class[@name='ImageViewTarget']/method[@name='setResource']" />

  <remove-node path="/api/package[@name='org.litepal.tablemanager']/class[@name='AssociationCreator']/method[@name='createOrUpgradeTable']" />

  <remove-node path="/api/package[@name='com.bumptech.glide.request.animation']/interface[@name='GlideAnimation.ViewAdapter']/method[@name='getView']" />

  <remove-node path="/api/package[@name='com.bumptech.glide.load.data']/interface[@name='DataFetcher']/method[@name='loadData']" />

  <remove-node path="/api/package[@name='com.bumptech.glide.load.engine']/interface[@name='Resource']/method[@name='get']" />

  <remove-node path="/api/package[@name='com.bumptech.glide.load.resource.bitmap']/interface[@name='BitmapDecoder']/method[@name='decode']" />

Error: does not implement inherited abstract member

So far, there is only 1 error left.

xamarin not implement

Right-click on Additions folder to add a new C# class.

xamarin new class

To fix the error, add the unimplemented method in OptionExt.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;

namespace Okio
{
    public sealed partial class Options
    {
        public override Java.Lang.Object Get(int index)
        {
            throw new NotImplementedException();
        }
    }
}

Although I can successfully build a .dll file for Xamarin now, the assembly is not perfect. Dynamsoft Camera SDK includes many obfuscated classes, such as a.class, b.class and so on. To generate an “un-obfuscated” C# type, use the snippet as follows:

<attr path="/api/package[@name='{package_name}']/class[@name='{name}']" 
    name="obfuscated">false</attr>

If you want to use Dynamsoft Camera SDK for Xamarin, you have to process all obfuscated classes by yourself.

In a nutshell, to create Android apps relying on a third-party Android library built as an AAR bundle, you should give priority to Java and Kotlin rather than binding the library for Xamarin. Unless the third-party Android library is created in C#.

Source Code

https://github.com/dynamsoft-dcs/xamarin-android-metadata