Xamarin for Cordova – a bizarre idea?

from on 16.09.2016
6
hand reichen

The goal

The main task set by the Cross-Platform .NET topic at the annual Zühlke Germany camp in Barcelona was how to combine Cordova and Xamarin to use them together. At first, this idea seems to be a bit crazy because both frameworks are trying to solve the same problem. Xamarin as well as Cordova are cross-platform frameworks which were created to simplify the development of multi-platform mobile apps. Both with fundamentally different approaches but the same result: a native app bundle. While Xamarin offers the ability to write apps completely using the C# (or F#) programming language, Cordova is more of a native wrapper for web apps. If you want to learn more about both approaches, you can follow these links: Xamarin | Cordova.

But we identified use cases where there can be a real benefit to combining the two technologies. For example, if a customer wants to integrate his existing C# business logic into an existing Cordova app. One option would be the rewriting of all exisiting code in the common language. That would be mean investing substantial effort. A combination of Xamarin and Cordova could solve this problem really well.

Additional use cases could be:

  • Cordova projects, which need a lot of plugin code benefit directly from the possibility to write the code in C#, because the plugins can be reused over the different platforms.
  • Existing Xamarin plugins can be reused in Cordova projects. Xamarin plugins are typically maintained better than Cordova plugins.
  • Easy porting of existing web apps with C# backends to mobile.
  • Using the possibilities of the entire .NET framework in a Cordova app.

That means?

Cordova provides two main features for app development:

  • A native web view, that presents the included web app to the user.
  • It allows the access and usage of native code snippets (e.g. Objective-C for iOS and Java for Android). These so-called Cordova plugins are used to access native platform features from the web app code. This allows accessing platform functionality that would normally not be accessible using web technology only. An example would be the fingerprint scanner plugin.

Some advantages of a combined solution of Xamarin and Cordova are:

  • Existing Cordova apps can be executed as usual.
  • Plugins can be written in C# (or F# and even Visual Basic .NET).
  • Existing C# code can be used together with the Cordova app.

Our approach

There are two approaches which can be used to accomplish the goal:

  • Including the existing Cordova library for iOS and Android through bindings into the platform-specific Xamarin projects.
  • Creating an own web view in Xamarin (maybe even with Xamarin.Forms) and “reimplementing” the Cordova functionality.

We chose the first attempt, because it is possible to reuse existing Cordova plugins this way. If you’re interested in the other approach, you can read about it in Shawn Anderson’s blog.

Architecture

To build apps based on Cordova the according libraries are needed. Those libraries are written in the native programming language of the respective platform and provide the Cordova functionalities to the app. We now want to include the Android as well as the iOS library into a Xamarin project. To accomplish this, a Xamarin binding project is needed for each platform. The binding project gets included into the respective Xamarin project afterwards (Android and iOS). The following image shows the proposed architecture:

Setup

Cordova for Xamarin.Android

Cordova-Android binding

To create an Android binding project, the Cordova Android library is needed as a jar file. To build the needed jar file execute the following steps.

Install ant with the help of Homebrew:

brew install ant

Clone the Cordova Android project:

https://github.com/apache/cordova-android/tree/master/

Switch to the cordova-android/framework folder and execute:

ant jar

Through this, the file cordova-xxx.jar gets created. The built jar file can be found in the following directory: cordova-android/framework.

Now create a Xamarin Binding Project and use the previously created jar file. Change the build-action to EmbeddedJar as described in the documentation.

Xamarin.Android project

After the binding project has been created successfully, another project can be added to the solution. This time it’s the Xamarin.Android project. To get access to the necessary Cordova functions, the previously built binding library gets referenced by the newly created Xamarin.Android project.

Xamarin Reference

Update the MainActivity.cs to the following code snippet, so the Cordova view gets executed on app start.

				[Activity(Label = "app.droid", MainLauncher = true, Icon = "@mipmap/icon")]
				public class MainActivity : CordovaActivity
				{
				   public override void OnCreate(Bundle savedInstanceState)
				   {
				       base.OnCreate(savedInstanceState);
				       LoadUrl("file:///android_asset/www/index.html");
				   }
				}
				

After Cordova has been installed, the command:

				cordova create hello com.example.hello HelloWorld
				

creates an example app. The command creates a raw Cordova app, which is missing the according platforms. Add your desired platform with the following commands:

				cordova platform add android --save
				
				cordova platform add ios --save
				

Copy the assets folder into the Xamarin project. This folder contains the whole web app (e.g. *.html and *.js files).

				HelloWorld/platforms/android/assets
				

After importing the assets folder, change the BuildAction to AndroidAsset. This is the correct way to tell Xamarin how to handle imported files.

Xamarin.Android Assets

Now it should be possible to build and run the project. The Cordova logo should be visible on the smartphone also.

Xamarin.Android Plugins

Plugins are used to communicate between C# code and the Cordova logic. With the plugin concept you could simply use any of your C# business logic inside of a Cordova app. If you want to access native features like the camera or file system, plugins are useful, too. The next steps will show you how to implement a simple Xamarin.Android Plugin.

Create a new class inside the Xamarin.Android project and inherit from CordovaPlugin.

				public class TestPlugin : CordovaPlugin
				{
				    protected override void PluginInitialize()
				    {
				        base.PluginInitialize();
				    }
				
				    public override bool Execute(string action, CordovaArgs args, CallbackContext p2)
				    {
				        if (action.Equals("echo"))
				        {
				            var message = args.Get(0).ToString();
				            Echo(message, p2);
				            return true;
				        }
				        return false;
				    }
				
				    private void Echo(String message, CallbackContext callbackContext)
				    {
				        if (message != null && message.Length > 0)
				        {
				            callbackContext.Success(message + " from Xamarin!");
				        }
				    }
				}
				

The Execute method can be invoked from the Cordova code afterwards.

Create a new file called testplugin.js in the folder Assets/www. This file is the Cordova equivalent to the previously created C# class and manages the communication from the Cordova side.

				var TestPlugin = function() {
				};
				
				TestPlugin.prototype.echo = function(str, callback) {
				    cordova.exec(callback, function(err) {
				        callback();
				    }, "TestPlugin", "echo", [str]);
				};
				

The plugin can be used with the following sample code snippet:

				var testPlugin = new TestPlugin();
				
				testPlugin.echo("Good Evening", function(echoValue) {
				    alert(echoValue);
				});
				

The plugin is dedicated to transport the string Good Evening to the Xamarin code. The Xamarin code attaches from Xamarin to the string and returns the result. Afterwards the Cordova app shows an alert window with the respective result.

Cordova for Xamarin.iOS

Cordova iOS Binding

The iOS project needs a Cordova binding as well. To achieve that, the original iOS Cordova library is needed. Clone that library from the source https://github.com/apache/cordova-ios. To make things work with Xamarin.iOS we need to accomplish a tiny customization here. So open the file CDVCommandQueue.m and find the following line if (!([obj isKindOfClass:[CDVPlugin class]])) {. Change the code to:

				if (!([obj isKindOfClass:[CDVPlugin class]]))
				{
				    NSLog(@"ERROR: Plugin '%@' not found, or is not a CDVPlugin. Check your plugin mapping in config.xml.", command.className);
				}
				

After changing the code, a static library (which is needed for the binding project) can be built. Xamarin’s documentation explains pretty well how to do that. Finally use the following makefile to create a fat binary:

				XBUILD=/Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild
				PROJECT_ROOT=.
				PROJECT=$(PROJECT_ROOT)/CordovaLib.xcodeproj
				TARGET=CordovaLib
				TARGETMV=Cordova
				
				all: libCordova.a
				
				libCordova-i386.a:
					$(XBUILD) -project $(PROJECT) -target $(TARGET) -sdk iphonesimulator -configuration Release clean build
					-mv $(PROJECT_ROOT)/build/Release-iphonesimulator/lib$(TARGETMV).a $@
				
				libCordova-armv7.a:
					$(XBUILD) -project $(PROJECT) -target $(TARGET) -sdk iphoneos -arch armv7 -configuration Release clean build
					-mv $(PROJECT_ROOT)/build/Release-iphoneos/lib$(TARGETMV).a $@
				
				libCordova.a: libCordova-i386.a libCordova-armv7.a
					xcrun -sdk iphoneos lipo -create -output $@ $^
				
				clean:
					-rm -f *.a *.dll
				

Then, the binding project can be built. To make the binding work, we need the ApiDefinition.cs and Struct.cs. Both files were modified by us to fit the requirements.

Xamarin.iOS project

Like with the Android project, an iOS project is needed as well. So create a new Xamarin.iOS project and reference the previously built binding library. The steps are almost equivalent to the Android project.

In preparation, we already built a Cordova iOS app. Like before, we need the assets folder in our Xamarin.iOS project. This folder contains the web app inside the Xamarin project. The steps are just like in the Android project.

After the assets folder is copied into our project and the binding library is referenced, the class ViewController.cs needs some logic to run the Cordova app. So change the ViewDidLoad method as seen here:

				public override void ViewDidLoad ()
				{
				     base.ViewDidLoad ();
				
				     _cdv = new CDVViewController ();
				     _cdv.Init();
				     _cdv.WwwFolderName = "www";
				     _cdv.StartPage = "index.html";
				
				     var constraints = new []
				        { NSLayoutAttribute.Top, NSLayoutAttribute.Bottom, NSLayoutAttribute.Left, NSLayoutAttribute.Right }
				        .Select(attr => NSLayoutConstraint.Create(_cdv.View, attr, NSLayoutRelation.Equal, View, attr, 1, 0)).ToArray();
				
				     Add(_cdv.View);
				     View.AddConstraints(constraints);
				}
				

If we run the project now, we should see the Cordova initial screen on the iOS device.

Xamarin.iOS plugin

To create a plugin, a respective class is needed inside the Xamarin.iOS project. This class needs to inherit from CDVPlugin, so Cordova can recognize it as a plugin. You can view our sample plugin below:

				[Register("Sample")]
				public class Sample : CDVPlugin
				{
				    [Export("SayHello:command")]
				    public void SayHello(CDVInvokedUrlCommand command)
				    {
				        string commandParam = string.Empty;
				        if (command.Arguments.Any())
				        {
				            commandParam = command.Arguments.First();
				        }
				
				        var res = CDVPluginResult.ResultWithStatusMessagesArray(CDVCommandStatus.Ok, (NSString)"Hello", (NSString)commandParam);
				
				        var cdvVC = (CDVViewController)ViewController;
				        var myNewCmd = new CDVCommandDelegateImpl(cdvVC);
				        myNewCmd.SendPluginResult(res, command.CallbackId);
				    }
				}
				

This plugin will simply transport the Hello string, but it’s sufficient as a proof of concept.

To register the plugin, add the following lines to your ViewDidLoad() method, inside the ViewController.cs class. Just after the line: _cdv.StartPage = "index.html";.

				var samplePlugin = new Sample();
				_cdv.RegisterPluginClassName(samplePlugin, "Sample");
				
				var demo = _cdv.PluginObjects;
				demo.Add((NSString)"Sample", samplePlugin);
				
				samplePlugin.PluginInitialize();
				samplePlugin.CommandDelegate = _cdv.CommandDelegate;
				

Xamarin4Cordova on Safari

Xamarin4Cordova on iOS

Conclusion

The main finding: It’s definitely possible to use Xamarin and Cordova together. Although the integration is not very intuitive (we didn’t expect it to be, either), the integration seems to be not very clean and a lot of manual adjustments are needed. Significant effort is still needed to make cordova platform add xamarin-android and cordova platform add xamarin-ios work out of the box. If you stick to the steps we showed you above you will likely still have to invest a lot of effort for each platform update. This should defintely be considered before planning a real world project.

But against all the odds, the combination of both technologies is possible and works pretty well in the end. Whether it is a good choice for your project depends on a lot of factors, including your team know-how, the platforms you are trying to support, any legacy code you might have, and other factors.

Please feel free to contact us if you have any questions!
Robin and Kerry

Write a comment

Your e-mail address will not be published or shared with third parties. Fields marked with * are required.

6 comments for “Xamarin for Cordova – a bizarre idea?

  1. Martin Cisneros

    The post says that with this combintion youll get a native webview.. but Cordova by itself its a native webview isnt it ?

    1. Kerry Lothrop

      Yes, Cordova uses a native web view. We're embedding the library that contains the native web view into our Xamarin application.

      You'll have to distinguish between a native web view (generated using Objective-C) and a Xamarin native web view (generated using Xamarin). Our approach was the former.

  2. Mark Downes

    Does the plugin need to be somehow registered in a plugin.xml file or does the CordovaPlugin base class take care of that?

  3. Rudy Bisiaux

    Hi, i'l trying to embend a ionic app inside my Xamarin application. The ionic part show itself in my app bug my plugin don't work at all. I have a error like "Class not found". I try to register this plugin in the plugin.xml but it doen't work either. Could you help me with this ?

    Thanks