Android ecosystem has huge numbers of libraries which can be used in Android-based applications. Technically, these libraries are files with .jar or .aar extensions. When developing Android application with Java these libraries can be directly included/linked in the application and used. Of course, re-usage of these java-based libraries in Xamarin Forms is possible, but the process is not so straightforward.
In this blog post I will show how to import Android Java library into Xamarin Forms and use its functionality in C# application.
To make all more real-word, I will try to import one random jar library from public repository https://mvnrepository.com and try to use the logic from it.
Let’s get our hands dirty
As I sad before, for the purpose of this demo I randomly picked one library from mentioned repository. I picked HTML parser library located here https://mvnrepository.com/artifact/org.jsoup/jsoup. You can find more information and documentation about the library here https://jsoup.org.
First, In Visual Studio 2019 I create new Xamarin.Forms project with Blank Template. I select only Android platform. In one of the next blog posts I will show similar procedure for iOS – how to reuse Objective-C iOS code (and .a library) in Xamarin Forms and C#. But for now, let’s focus on Android.
As usual, Visual Studio scaffolds two projects, one is Common Project (cross-platform shared code) and the other is Android specific project. Next, I add new “Android Binding Library (Xamarin)” project to handle bindings between imported Java library and C#/Xamarin runtime. This binding library will create proxies (or Managed Callable Wrappers or just wrappers for that mater) which will handle C# calls and delegate them to external Java library.
My solution structure now consists of three projects:
I have everything ready and prepared so I can start putting external “logic” in my app.
Importing external Android Java library
I download latest version of jsoup library from https://mvnrepository.com/artifact/org.jsoup/jsoup/1.12.1. I also check maven definition for any dependencies. All library dependencies must be also embedded in the binding assembly . In my case, jsoup library has no dependencies, as you can see from maven definition below:
Procedure is simple, download jar file, put it into Jars folders in bindings project, mark file as “EmbeddedJar” and that’s it.
Now, things start to get interesting. At project compile, EmbeddedJar
build action/process starts to create endpoint/stubs between C# and Java. This are so called Managed Callable Wrappers.
If you know that your Java library is available on the target device, you can also use InputJar
option for Build Action.
Almost always, when I do first compile I get a lot of compile errors, just like pictured below:
Hold on, no panic!
Due to differences between Java and C#, no all Java-to-C# relations can be translated 1:1. Normally, I observed each and every error and I just remove problematic code. For this purpose Transformation/Metadata.xml file with developer-specific directives how to handle code generation (mapping Java code to C# wrappers) is used. You can check awesome documentation about this here: Metadata.xml Transform file. In my case, I simply removed problematic code, so my binding transformation file looks like this:
1 2 3 4 5 6 7 8 9 |
<metadata> <remove-node path="/api/package[@name='org.jsoup.nodes']/class[@name='Attribute']" /> <remove-node path="/api/package[@name='org.jsoup.select']/class[@name='Evaluator']" /> <remove-node path="/api/package[@name='org.jsoup']/interface[@name='Connection.KeyVal']/method[@name='value' and count(parameter)=1 and parameter[1][@type='java.lang.String']]" /> <remove-node path="/api/package[@name='org.jsoup']/interface[@name='Connection.KeyVal']/method[@name='key' and count(parameter)=0]" /> <remove-node path="/api/package[@name='org.jsoup']/interface[@name='Connection.KeyVal']/method[@name='inputStream' and count(parameter)=1 and parameter[1][@type='java.io.InputStream']]" /> <remove-node path="/api/package[@name='org.jsoup']/interface[@name='Connection.KeyVal']/method[@name='inputStream' and count(parameter)=0]" /> <remove-node path="/api/package[@name='org.jsoup']/interface[@name='Connection.KeyVal']/method[@name='key' and count(parameter)=1 and parameter[1][@type='java.lang.String']]" /> </metadata> |
I hit Rebuild and now my compiler report only some warnings. For now, I am good with this, but in production ready app, these warnings should be checked and fixed if possible.
If you are more interested in generated code (generated Managed Callable Wrappers a.k.a proxies between Java and C#) you can checked output in obj folder (code intermediate folder) as shown below:
Finally, I have build my binding project. Now I need to reference it to my Xamarin Android project and use the C# wrappers to call logic in Java library.
Integration into my mobile application
Binding library is ready to be used by my mobile app. I will use jsoup HTML parsing library wrapper to parse title (<title></title>) element from web pages.
Android Permissions
On start and not to forget, my Android application will use internet, so I need to put correct permissions in my Android Manifest:
1 2 3 4 5 6 7 |
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="si.jenx.androidbinding"> <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28" /> <application android:label="Jenx.AndroidBinding.Android"></application> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.INTERNET" /> </manifest> |
Integration steps
As said, when binding library is referenced in Android project It can be used only on platform specific project. If I want to use it in shared code project (where all logic normally is) I do this by service Dependency Injection mechanism. I will just quickly summarize steps how this can be done.
I define interface in my shared project called “IGetWebSiteTitleService
“
1 2 3 4 5 6 7 8 9 10 |
using System; using System.Threading.Tasks; namespace Jenx.AndroidBinding.Services { public interface IGetWebSiteTitleService { Task<string> GetWebPageTitleAsync(Uri url); } } |
Implement interface in Android specific project
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
using Jenx.AndroidBinding.Services; using System; using System.Threading.Tasks; namespace Jenx.AndroidBinding.Droid.Services { public class GetWebSiteTitleService : IGetWebSiteTitleService { public Task<string> GetWebPageTitleAsync(Uri url) { // need to work on non-ui thread -> exception of type Android.Os.NetworkOnMainThreadException is thrown var workTask = Task.Run(() => { try { var doc = Org.Jsoup.Jsoup.Connect(url.AbsoluteUri).Get(); return doc.Title(); } catch { return "N/A"; } }); return workTask; } } } |
Injected into shared project via App constructor and make it application visible
Here, for simplicity, I used App static field to enable my service availability in my application, but It can be also used by any dependency injection container and used across application.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
using Jenx.AndroidBinding.Services; using Xamarin.Forms; namespace Jenx.AndroidBinding { public partial class App : Application { public static IGetWebSiteTitleService GetWebSiteTitleService { get; private set; } public App(IGetWebSiteTitleService service) { InitializeComponent(); GetWebSiteTitleService = service; MainPage = new MainPage(); } protected override void OnStart() { } protected override void OnSleep() { } protected override void OnResume() { } } } |
Now, that my service is available in my application I can use it in my code-behind, e.i:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
using Android.App; using Android.Content.PM; using Android.OS; using Android.Runtime; namespace Jenx.AndroidBinding.Droid { [Activity(Label = "Jenx Android Binding", Icon = "@mipmap/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)] public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity { protected override void OnCreate(Bundle savedInstanceState) { // removed for brevity LoadApplication(new App(new Services.GetWebSiteTitleService())); } // removed for brevity } } |
Gui
Last, but not the least important thing is GUI or user-interaction layer. My mobile app needs to communicate with service layer and present result to the end user. Just for illustration, my app use simple code-behind approach, but logic can be used also with MVVM pattern (by consuming IGetWebSiteTitleService
service in some ViewModel
and bind result to View
).
My XAML is simple…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:d="http://xamarin.com/schemas/2014/forms/design" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" Visual="Material" x:Class="Jenx.AndroidBinding.MainPage"> <StackLayout> <Label Text="" HorizontalOptions="Center" x:Name="WebPageTitle" /> <Entry Placeholder="https://www.jenx.si" Text="https://www.jenx.si" x:Name="GetWebPageUrl" /> <Button Text="Get Title" Clicked="Button_Clicked" /> </StackLayout> </ContentPage> |
… with only one event handler for button click which calls underlying service to process the request.
1 2 3 4 |
private async void Button_Clicked(object sender, System.EventArgs e) { WebPageTitle.Text = await App.GetWebSiteTitleService.GetWebPageTitleAsync(new Uri(GetWebPageUrl.Text)); } |
Result
End user enters URL, hit “Get Title” button and gets web page title parsed! All this logic is executed by external Java library, in my case by jsoup.
Conclusion
Binding Android Java libraries into Xamarin Forms applications is very powerful mechanism because It enables Xamarin developers to use numerous Android Java libraries available.
In this blog post, I described practical example (with all problems and drawbacks) of importing Android Java library into Xamarin Forms application.
Happy coding!
2 thoughts on “Using Android Java libraries in Xamarin Forms – a practical example.”
This is great! Thanks for sharing this one, do you have iOS blog of the same ? Thanks in advance.
hi, thanks so much. But i have an error on my compilation (i don’t know how solve it):
Severity Code Description Project File Line Suppression State
Error CS0534 ‘Element’ does not implement inherited abstract member ‘Node.Empty()’ pruebaBindingNativeJava C:\Users\tom\source\repos\sln_pruebaMovilTelefonos\pruebaBindingNativeJava\obj\Debug\generated\src\Org.Jsoup.Nodes.Element.cs 10 Active
What can I do??