In SABOMobile IT, we use Xamarin Bindings for Usercentrics In-App SDK. Usercentrics is a Consent Management Platform to get GDPR, CCPA, LGPD compliant for websites and apps. Our goal was to show consent dialog to user in our Xamarin.Androidand Xamarin.iOS app and store it in central consent storage for both web and apps.
Usercentrics guys offer their SDK written on the Kotlin multiplatform, so the Android package is in the Kotlin language and the iOS package is in the transformed Objective-C language.
After all the steps described above, we’ve ended up with a Xamarin bindings library of Usercentrics for Android (Picture)
Whereas in the Additions folder, there are almost all missing empty implementations of interfaces created during transformation (which is performed during the build process), Usercentrics.cs is a partial class extending the original transformed com.usercentrics.sdk.usercentrics object with C# implementation of the Kotlin native IFunction event.
In the Jars folder, you can see the original usercentrics-1.12.0.aar Android package (with Build Action set to “LibraryProjectZip”) and necessary third party .jar references used internally by the .aar package.For all .jar files, build action in Visual Studio is set to“EmbededReferenceJar”.
In the Transforms folder, EnumFields.xml and EnumMethods.xml are empty. Metadata.xml needs the following transformation to compile the Android Bindings Library without build errors:
{% c-block language="xml" %}
<metadata>
<attrpath="/api/package[@name='com.usercentrics.sdk.models.tcf']/class[@name='TCFFirstLayerDescription']/constructor[@name='TCFFirstLayerDescription' and count(parameter)=3 and parameter[1][@type='java.lang.String'] andparameter[2][@type='java.lang.String'] and parameter[3][@type='java.lang.String']]/parameter[@name='default']" name="name">defaultStr</attr>
<attrpath="/api/package[@name='com.usercentrics.sdk.models.tcf']/class[@name='TCFFirstLayerDescription']/method[@name='copy' and count(parameter)=3 and parameter[1][@type='java.lang.String'] andparameter[2][@type='java.lang.String'] andparameter[3][@type='java.lang.String']]" name="name">defaultStr</attr>
<attrpath="/api/package[@name='com.usercentrics.sdk.models.tcf']/class[@name='TCFFirstLayerDescription']/method[@name='defaultStr' and count(parameter)=3 and parameter[1][@type='java.lang.String'] and parameter[2][@type='java.lang.String'] and parameter[3][@type='java.lang.String']]/parameter[@name='default']" name="name">defaultStr</attr>
<attrpath="/api/package[@name='com.usercentrics.sdk']/class[@name='CategoryProps']/constructor[@name='CategoryProps' and count(parameter)=2 and parameter[1][@type='com.usercentrics.sdk.models.settings.UCCategory'] and parameter[2][@type='boolean']]/parameter[@name='checked']" name="name">isChecked</attr>
<attrpath="/api/package[@name='com.usercentrics.sdk']/class[@name='CategoryProps']/method[@name='copy' and count(parameter)=2 and parameter[1][@type='com.usercentrics.sdk.models.settings.UCCategory'] and parameter[2][@type='boolean']]/parameter[@name='checked']" name="name">isChecked</attr>
<attrpath="/api/package[@name='com.usercentrics.sdk']/class[@name='PurposeProps']/constructor[@name='PurposeProps' and count(parameter)=3 and parameter[1][@type='boolean'] and parameter[2][@type='boolean'] and parameter[3][@type='com.usercentrics.sdk.services.tcf.interfaces.TCFPurpose']]/parameter[@name='checked']" name="name">isChecked</attr>
<attrpath="/api/package[@name='com.usercentrics.sdk']/class[@name='PurposeProps']/method[@name='copy' and count(parameter)=3 and parameter[1][@type='boolean'] and parameter[2][@type='boolean'] and parameter[3][@type='com.usercentrics.sdk.services.tcf.interfaces.TCFPurpose']]/parameter[@name='checked']" name="name">isChecked</attr
<attrpath="/api/package[@name='com.usercentrics.sdk']/class[@name='SpecialFeatureProps']/constructor[@name='SpecialFeatureProps' and count(parameter)=2 and parameter[1][@type='boolean'] and parameter[2][@type='com.usercentrics.sdk.services.tcf.interfaces.TCFSpecialFeature']]/parameter[@name='checked']" name="name">isChecked</attr
<attrpath="/api/package[@name='com.usercentrics.sdk']/class[@name='SpecialFeatureProps']/method[@name='copy' and count(parameter)=2 and parameter[1][@type='boolean'] and parameter[2][@type='com.usercentrics.sdk.services.tcf.interfaces.TCFSpecialFeature']]/parameter[@name='checked']" name="name">isChecked</attr
<attrpath="/api/package[@name='com.usercentrics.sdk']/class[@name='StackProps']/constructor[@name='StackProps' and count(parameter)=2 and parameter[1][@type='boolean'] and parameter[2][@type='com.usercentrics.sdk.services.tcf.interfaces.TCFStack']]/parameter[@name='checked']" name="name">isChecked</attr>
<attrpath="/api/package[@name='com.usercentrics.sdk']/class[@name='StackProps']/method[@name='copy' and count(parameter)=2 and parameter[1][@type='boolean'] and parameter[2][@type='com.usercentrics.sdk.services.tcf.interfaces.TCFStack']]/parameter[@name='checked']" name="name">isChecked</attr
<attrpath="/api/package[@name='com.usercentrics.sdk']/class[@name='VendorProps']/constructor[@name='VendorProps' and count(parameter)=3 and parameter[1][@type='boolean'] and parameter[2][@type='boolean'] and parameter[3][@type='com.usercentrics.sdk.services.tcf.interfaces.TCFVendor']]/parameter[@name='checked']" name="name">isChecked</attr>
<attrpath="/api/package[@name='com.usercentrics.sdk']/class[@name='VendorProps']/method[@name='copy' and count(parameter)=3 and parameter[1][@type='boolean'] and parameter[2][@type='boolean']and parameter[3][@type='com.usercentrics.sdk.services.tcf.interfaces.TCFVendor']]/parameter[@name='checked']" name="name">isChecked</attr>
<remove-node path="/api/package[@name='com.usercentrics.sdk']/class[@name='ApiLabels']/constructor"/>
</metadata>
{% c-block-end %}
We used this command to run the Sharpie Utility, which transforms the Objective-C header .h file to the corresponding ApiDefinitions.cs file (Picture).
{% c-block language="xml" %}
sharpie bind -sdkiphoneos14.5 ./Usercentrics.framework/Headers/Usercentrics.h -namespace Usercentrics -scope Usercentrics.framework/Headers -c -F
{% c-block-end %}
This means that we’ve bound the library corresponding to the iPhone OS 14.5 version, specifying namespace to “Usercentrics”.
The resulting Structs.cs file is empty and ApiDefinitions.cs is a pretty huge file with almost 12 thousand lines of code definition.
After we produce Xamarin bindings libraries both for Android and for iOS, we just wrap the SDK calls into platform specific services which implement the interface defined in the shared Xamarin.net standard project.
After this, we create our own UI for user consent interaction which calls the Usercentrics SDK in the background for storing and refreshing consents in the cloud.
What we’ve learned in this article: Creation of a Xamarin Bindings Library gives us the freedom to use .NET for native mobile apps, even if we have to use some native dependencies. The creation itself is a complex problem with platform / language specific pitfalls, but achieving this ability means a lot for Xamarin / MAUI developers. It raises their potential to work on any mobile apps related problem.
In SABOMobile IT, creation of the Xamarin Bindings Library enables us to offer requested functionality for our customers and allows us to fulfill all clients’ needs to the fullest.
Thank you for reading!
Luboš Brát, Senior software developer @ SABO Mobile IT
1) https://www.mono-project.com/docs/advanced/runtime/
2) https://docs.microsoft.com/en-us/xamarin/get-started/what-is-xamarin
3) https://www.xamboy.com/2020/07/20/creating-a-xamarin-binding-library-for-ios-and-android-part-1/
4) https://docs.microsoft.com/en-us/xamarin/android/platform/binding-java-library/
5) http://java-decompiler.github.io/
6) https://docs.usercentrics.com/#/in-app-sdk
7) https://www.w3schools.com/xml/xpath_syntax.asp
8) https://docs.microsoft.com/en-us/xamarin/cross-platform/macios/binding/objective-sharpie/
9) https://debruyn.dev/2016/creating-a-xamarin.ios-binding-project-for-dummies/
10) https://docs.microsoft.com/en-us/xamarin/xamarin-forms/deploy-test/hot-restart
Read previous: Xamarin Bindings Libraries, an Android part (1/3)
Read previous: Xamarin Bindings Libraries, and iOS part (2/3)