Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Xamarin.Android Binding to 3rd party SDK with dependencies

I am using VS2015 Update3 with current/up-to-date Xamarin, and am trying to create a binding library to wrap a 3rd party SDK written in Java. I have a C#/.Net background, with little Java experience thus far. This SDK depends on 3 other items, which are available as AAR's or via NuGet.

I was able to create a binding library for the parent AAR, with some MetaData.xml tweakery to adjust parameter types and return types and correct a class accessor to satisfy its abstract base class, all of which were initially preventing the resulting C# .Net Android binding library from building. Now the parent binding assembly builds, but can't execute, as it requires the other 3 AAR dependencies.

Dependencies needed ("#" is the alias for each when mentioned in the post below):

  • "2" -> com.android.support:support-v4:25.1.1
  • "3" -> com.google.android.gms:play-services-location:10.2.0
  • "4" -> com.google.android.gms:play-services-gcm:10.2.0

In this scenario, if I need to create a binding dll to wrap a parent AAR that depends on 3 other AARs:

Should I (A) create 4 binding assemblies - a parent binding assembly Bind_AAR_1.dll, that depends on Bind_AAR_2.dll, Bind_AAR_3.dll, and Bind_AAR_4.dll?

OR, should I (B) dig into the 2, 3, and 4 AAR files and get the JAR file from each, and include them in Bind_AAR_1.dll directly in the Jars folder using 'EmbeddedJar' (or other attribute/setting)?

OR, seeing that 2, 3, and 4 are actually availabe via NuGet, should I (C) reference them into the Bind_AAR_1 project using NuGet? Will the Javacode in the AAR 1 be able to see the code in AAR's 2, 3, and 4 if I use NuGet to reference/include them?

I've tried variations on each of methods (A), (B), and (C) with no luck yet, or resulting work that leads to more work, which leaves me wondering what is the best/right way to do this.

On a related topic, is it a best practice to use the Metadata.xml file to explicitly remove everything (classes, etc) from the resulting generated wrapper API that you do not directly plan to use in your Xamarin project? Seems like it might be good to limit the interface and perhaps also reduce the build size (and possibly build time) of the binding dll.

Any help would be appreciated!

Curtis

=======================================================================

4/11/2017 - Update

Very glad to have input from the likes of @_JonDouglas! but... still not able to get this SDK working.

I have tried each of these methods A, B, and C, and have been able to get my bindings project to build (with manipulations done via transforming/MetaData.xml). The 3rd party SDK was designed/documented to be a part of / a component in a Java solution. Per the vendor's instructional documentation, this solution would have the 3 dependencies added to its .gradle file, thus providing the reference to these items in the overall Java solution. I have no Java history, so winging it here.

As it is, when I wrap the SDK (alone or with dependencies using any of the 3 methods) with Visual Studio into a Xamarin Android Binding Assembly, at runtime the SDK hits ClassNotFound exceptions (evidenced by "Google Play not found." in the logs). The classes used from the required Java dependencies are created created in a late bound fashion using name strings for classnames like this: try { Class.forName("com.google.android.gms.common.GooglePlayServicesUtil");
//more code here } catch (ClassNotFoundException e) { Log.e("3rd Party SDK", "Google Play not found."); }
It doesn't seem to matter how I build the binding assembly in Visual Studio using any of the 3 methods above to include the dependency items (1 AAR + 3 Nuget OR 1 AAR + 3 Jars in 1 binding OR 4 AARs in 4 bindings using project references). At runtime, the SDK doesn't seem to see them. Perhaps that makes sense that it won't - there's no .gradle (like a csproj file) that points to them in the SDK itself?

I am thinking what I need to do is (closer to the vendor's instruction) create a Java solution complete with a .gradle file that includes the SDK as well as the 3 dependencies in the Java project, building to create an AAR file. Then, I believe I may be able to create a Xamarin binding assembly for the resulting encompasing AAR. Then I'll add a reference to this binding assembly to my .Andriod assembly within my solution and proceed.

Theory (yet to be proven): [Failed. See 4/12 update below.] Wrap the Java SDK + its coded dependencies (explicit in the .gradle) in a module/library, and then wrapping that in a Xamarin Android Binding library. Then all I'll need to do is figure out what public methods I need to use (and therefore expose) from this wrapper module/library, add these public methods and forward the calls to the SDK.

Big Picture: I'm trying to design and code this as a cross-platform Xamarin solution, but am working with an Android-specific and IOS-specific SDK that appear both designed to be part of the application/UI experience (designed to do more than just handle communication to the 3rd party). This UI aspect has yet to play out, as I've not seen either SDK function as of yet.

=======================================================================

4/12/2017 - Update

Theory (FAILED): Wrap the Java SDK + its coded dependencies (explicit in the .gradle) in a module/library, and then wrap that in a Xamarin Android Binding library. We coded, built, deployed, and tested this. I now see that the app at runtime gets into the first layer of Java code, but fails to find any referenced code lower than that (aka dependencies). The wrapper's code runs fine but the wrapper at runtime can't find the SDK's classes, nor even a simple Java test component that we attempted to use via the wrapper. This is equivalent to issue we had with the SDK in the first place when we simply created a bindings file for it alone, and referenced the rest into the binding assembly by JAR or via NuGet. The lower level code is still not found, only the first layer of java executes. Still trying to come up with a solution.

=======================================================================

4/13/2017 - Update

I have made some progress. One step forward, and maybe two steps back as they say.

I now realize that the 3rd party SDK needs more of Google Play Services than just the two dependencies. I decompiled the two items explicitly mentioned, and the named class is not in there. I started looking at the local Android SDK location (as configured in Visual Studio) and found 'play-services-6.5.87.AAR'. Including this in my binding assembly as an EmbeddedResourceJar finally got the SDK code to find the particular class listed in code above.

Using NuGet

I am questioning the value of the Java wrapper described above, since everything needed for dependencies is now included as an EmbeddedReferenceJar. Seems like I should just bind the SDK AAR directly (with no added Java wrapper layer). I am rethinking this and trying to just create the binding assembly pointed to the SDK AAR in the JARS folder, and then include Google Play Services using NuGet.

Proceeding with that, (using NuGet to reference Google Play Services) I'm now getting this error: No resource found that matches the given name (at 'value' with value '@integer/google_play_services_version'). {project name} {project path}\AndroidManifest.xml I've tried a LOT of different things to try to get rid of this build-time error, but have not had any luck.

Not Using NuGet

If I stick with using the JAR file from inside Google's 'play-services-6.5.87.AAR' as an embeddedResourceJar, I don't get the build errors, but I do get runtime errors that the SDK can't find Android manifest settings needed. If you have a project arranged like this, am I correct as to which Android Manifest is used? XXX PCL Project --> XXX.Android project <-- My guess is that this is where the AndroidManifest.XML file is located that will be used....? ----> XXX.Android.AARBinding project
------> JARS folder --> 3pVendorSDK.AAR

Still digging....

like image 965
Curtis Herrick Avatar asked Apr 06 '17 19:04

Curtis Herrick


2 Answers

How I finally got my 3rd Party Java SDK to work with Xamarin:

Since my solution is to be Cross-Platform, I have:

  • Assembly: MyApplication (PCL Portable)
  • Assembly: MyApplication.Android
  • Assembly: MyApplication.Android.Binding

    (Wrapping the 3rd party SDK, provided as an AAR file)

The respective .IOS projects/assemblies will follow this naming pattern, when we get to that.

Android Binding Assembly

  • Jars Folder
    • Include My3rdParty.AAR file
      Set with Build Action: ‘LibraryProjectZip‘
  • Transforms Folder
    • Metadata.XML - I had to make adjustments to how the binding code was to be generated by Visual Studio to make sure types align, etc.
    • I had some methods that returned types that did not map well to .Net, so I used attr elements to adjust their 'managedType' and 'managedReturn' type values.
      Note: You can use more than one attr element on one method to get it tweaked as desired.
    • I had to remove a method (by using a remove-node element) that was declared as virtual and re-add it in custom code as an override to satisfy the abstract class definition of its base class.
  • Root Folder
    • CustomCode.cs - Re-add the one method that needed to be changed from private to public (it was removed via MetaData.xml). (note though that the resulting method will be non-functional, as it is just an empty stub to satisfy the abstract class inheritance)

Android Assembly

  • Manifest

    • Lacked accurate/complete SDK Manifest documentation.
      The Sdk manifest info changes were made here in the Android Assembly.
  • References

    • Add a reference to the Android Binding Assembly.
  • Components (NuGet)

    • Since my 3rd Party's SDK had documented dependencies on Google Play Services, and I'd decompiled their code using JD-GUI to verify what classes/etc it was trying to use, I added Xamarin.GooglePlayServices.Gcm to this project, not the Binding Assembly project.

      Note: It wasn't clear to me in any documentation that I'd read what level to bring in the AAR's dependencies. In one of my many attempts, I had gone down the path of adding them as JAR's directly (extracted from AAR's) to the Binding Assembly's JARS folder with a BuildAction of EmbeddedResourceJar. This provided slightly better run-time results as required items were found, but it felt... icky... Seemed like a recipe for maintenance issues. There had to be a better way. Enter NuGet!

      Note: Adding this one item brings along all of the following items:
    • Xamarin.GooglePlayServices.Base
    • Xamarin.GooglePlayServices.Basement
    • Xamarin.GooglePlayServices.Gcm (the one added via VS Manage NuGet)
    • Xamarin.GooglePlayServices.Iid
    • Xamarin.GooglePlayServices.Tasks
  • Assets

    • My 3rd Party's SDK used a config.JSON file, which needed to be put here so that at runtime it can read it.
      Set with Build Action: ‘AndroidAsset’
  • Build Time Additional work needed:

    • Additional MetaData.XML changes in the Binding Assembly.
      - Some of the generated code had new build errors that occurred at this point, when the compiled Binding Assembly is referenced by the Android assembly and a build was attempted.

Assembly Binding Transforms

As stated above, when I built MyApplication.Android.Binding, it built fine on its own. When I referenced it in MyApplication.Android, though, it generated a new Java code translation build error about the scope of a particular class not being as expected/required. To address this, I went back to the Binding assembly’s MetaData.xml file and chose to remove the problem class from the generated Java code interface since I did not have plans to call or use.

TIP: The easiest way to do this (that I've found) in Visual Studio is to enable the Show All Files feature on the BindingAssembly project, and then look in the obj\Debug\generated\src\ folders for the problem class. Once you find it, click on it and the generated Java interface code has the XPath statements generated as comments. These are what you need to either alter or remove the problem classes or methods using the MetaData.XML file.

Wrap-Up

Now all I need to do is write my cross-platform code on top (using Dependency Inversion + Xamarin Forms built-in DependencyService to reach each SDK at runtime) and my app should be good to go!

Hopefully these findings are helpful to others who need to work through a similar binding scenario.

If you do see any content here that is incorrect, or if you have anything you'd like to add, please comment!

like image 123
Curtis Herrick Avatar answered Oct 25 '22 00:10

Curtis Herrick


You should be able to do #3 which you simply reference the pre-built NuGet packages of these existing bindings of GPS/support libraries.

Please make sure that you use the exact version the binding library expects as google moves classes all the time.

My personal recommendation is the following in order of difficulty (easy -> hard):

  1. NuGet Package
  2. EmbeddedReferenceJar / ReferenceJar of the .jar or classes.jar (Use a LibraryProjectZip if there are resoruces)
  3. Binding projects for each dependency

As a best practice, yes you should use <remove-node> on items that you will not use. However this is mainly used when errors are being generated by code you won't use in the first place. I would just keep the items around regardless here unless there are quite a bit of methods you will never touch so you can be mindful of your dexcount.

For further help you can view my bindings guide: https://gist.github.com/JonDouglas/dda6d8ace7d071b0e8cb

like image 1
Jon Douglas Avatar answered Oct 25 '22 02:10

Jon Douglas