Cordova für Xamarin iOS
Motivation
Die Vor- und Nachteile von Hybrid-Apps wurden ja bereits ausführlichst auf diversen Plattformen und Blogs diskutiert, deswegen werden wir hier auf diesen Punkt nicht eingehen.
Aber wenn man bereits große Xamarin-Apps implementiert hat, wird man Libraries für diverse Funktionalitäten geschrieben haben. Sei es Logging, Datenhaltung, Business-Funktionalität oder komplexere Funktionalitäten, die Liste lässt sich unendlich fortführen.
Diesen Code neu zu schreiben, um ihn in einer Hybrid-App zu verwenden, "scheint nach zu viel Aufwand" bzw. kann sogar, aus architektonischen oder sprachlichen Gründen unmöglich sein.
Also haben wir beschlossen, die Funktionalität von Cordova auf .Net zu portieren und so unsere bereits bestehenden Libraries in diesen Apps nutzbar zu machen. Das ermöglicht es uns auch das große Angebot an bestehender .Net-Funktionalität in unserer Hybrid-App zu nutzen.
Speziell haben wir hier ein Fehlerprotokoll in eine Legacy App eingebaut.
Vorteile
- Globales Fehlerlogging der WebView im Xamarin Projekt
- Nutzen des .Net Framework
- Angenehmeres Debugging
- Portieren von bereits bestehenden Backends in .Net
- Xamarin Plugins in Cordova benutzen
Nachteile
- (Out of the Box Support nur bis zur Cordova Version 3.9.2, Support des InAppBrowsers ab 4.0 nur über Plugin)
- Weitere Schicht an Schnittstellen, die Fehleranfälligkeit besitzen
- Umständlich
Agenda
- Cordova Binaries erstellen
- Api Defintion generieren
- Bindings Projekt erstellen
- Projekt bereinigen und modifizieren
- Library erstellen
- Integrieren in bestehende iOS-App
- Plugin erstellen
- Plugin zu App hinzufügen
How to Start
Als erstes checken wir uns eine lokale Kopie des Cordova iOS Projektes aus, hier haben wir im Speziellen auf die Version 3.6.3 zurückgegriffen.
Cordova-iOS
Diese kopieren wir auf unseren Mac-Buildhost.
Als nächstes brauchen wir einen Makefile, um uns die Fat Binaries zu unserem Cordova-iOS Projekt erstellen zu lassen.
XBUILD=/Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild PROJECT_ROOT=./CordovaLib PROJECT=$(PROJECT_ROOT)/CordovaLib.xcodeproj TARGET=CordovaLib all: CordovaLib.a CordovaLib-i386.a: $(XBUILD) -project $(PROJECT) -target CordovaLib -sdk iphonesimulator -configuration Release clean build -mv $(PROJECT_ROOT)/build/Release-iphonesimulator/libCordova.a $@ CordovaLib-armv7.a: $(XBUILD) -project $(PROJECT) -target CordovaLib -sdk iphoneos -arch armv7 -configuration Release clean build -mv $(PROJECT_ROOT)/build/Release-iphoneos/libCordova.a $@ CordovaLib-arm64.a: $(XBUILD) -project $(PROJECT) -target CordovaLib -sdk iphoneos -arch arm64 -configuration Release clean build -mv $(PROJECT_ROOT)/build/Release-iphoneos/libCordova.a $@ CordovaLib.a: CordovaLib-i386.a CordovaLib-armv7.a CordovaLib-arm64.a xcrun -sdk iphoneos lipo -create -output $@ $^ clean: -rm -f *.a *.dll
Hierbei ist es extrem wichtig, dass die vorhandenen Leerzeichen und Abstände beim Kopieren übernommen werden, da das Makefile andernfalls nicht mehr funktionieren wird.
Die Makefile-Datei muss anschließend eine Ebene über unserem CordovaLib Projekt gespeichert werden.
Die Xamarin Dokumentation bietet auch eine anschauliche Erklärung der Schritte und einige weitere Details zum Erstellen des Binding-Projektes.
Wenn wir nun make ausführen wollen, müssen wir in das Verzeichnis des Makefile navigieren und make eingeben. Nun rollt eine Welle an Text über die Commandline.
Wir sollten am Ende eine BUILD SUCCEEDED und 4 *.a, „CordovaLib-i386.a“ „CordovaLib-armv7.a“ „CordovaLib-arm64.a“ „CordovaLib.a“ sehen. Wenn alles erfolgreich funktioniert hat, sehen wir außerdem die erstellten Dateien neben unserem Makefile.
Das C# Binding Projekt
Der nächste Schritt auf unserer Liste ist das Erstellen des .Net Binding-Projektes.
Dafür müssen wir uns die Library Bindings ApiDefinition.cs und die StructsAndEnums.cs Dateien generieren lasse. Diese kann man auch von Hand erstellen, aber Xamarin hat dafür bereits ein Tool out of the Box, Sharpie, das uns diesen Schritt abnimmt.
Wir nehmen wieder eine Commandline unserer Wahl, hier Putty, navigieren auf unserem Mac in den CordovaLib/Classes Ordner und führen auf der Commandline
sharpie bind --output=Bindings --namespace=Bindings.Cordova363 --sdk=iphoneos10.3 *.h
aus. Namespace steht hierbei für den .Net Namespace den wir später verwenden wollen. Die sdk ist die auf unserem Mac installierte iOS Version, diese können wir mit "sharpie xcode –sdks" herausfinden. *.h generiert die Dateien aus allen vorhandenen Header-Dateien.
Der nächste Schritt ist das Erstellen, Einbinden Modifizieren und Kompilieren unseres .Net Binding-Projektes.
Dazu erstellen wir ein neues Projekt, unter Templates/Visual C#/iOS finden wir ein „Bindings Library (iOS) Template dieses erstellen wir, mit dem gewünschten Projekt Namen.
Als nächstes fügt man die „ApiDefinition.cs“, „StructsAndEnums.cs“ und „CordovaLib.a“ zu dem Projekt hinzu. Wenn man die „CordovaLib.a“ aufklappt, sollte diese eine „Cordova.linkwith.cs“ enthalten, dort kann man falls erforderlich, die Zielarchitekturen reduzieren.
Wenn wir jetzt die Dateien „StructsAndEnums.cs“ und „ApiDefinitions.cs“ öffnen, finden wir eine große Anzahl an [Verify] Flags über diversen Methoden. Diese müssen alle verifiziert und einige Datentypen geändert werden.
Fangen wir an mit der „StructsAndEnums.cs“. Hier gibt es ein [Verify (PlatformInvoke)] da wir hier tatsächlich native iOS Funktionen aufrufen ist dieser Aufruf ok und wir können die [Verify] Flags entfernen.
Der nächste Typ an [Verfiy] Flags ist „[Verify (StronglyTypedNSArray)]“. Hier kann man den expliziten Typ des Arrays angeben. In unserem Fall haben wir darauf verzichtet und alle Flags dieser Art entfernt.
Jetzt wird es ein wenig interessanter, bei allen „[Verify (MethodToProperty)]“ muss man sich den nativen iOS Code von Cordova anschauen und herausfinden ob hier die Umwandlung von Methode zu Property richtig war oder ob es doch ein Funktionsaufruf ist. Dann muss die ApiDefinition umgeändert werden auf die Signatur des entsprechenden Aufrufs.
Ein weiteres Flag, welches man mit einem Verifiy finden kann ist „[Verify (ConstantsInterfaceAssociation)]“ hier im speziellen z.B. unter dem Interface „Constants“. Dort muss man das Static von der Interface Deklaration entfernen und jedem Feld des Interfaces ein [Static] Flag hinzufügen.
Des Weiteren gibt es noch einige Methoden und Signaturen die nicht eindeutig sind.
„CDVPluginResult ResultWithStatus (CDVCommandStatus statusOrdinal, int errorCode)“ und „CDVPluginResult ResultWithStatus (CDVCommandStatus statusOrdinal, int theMessage)“
An dieser Stelle muss man einzelne Funktionen umbenennen, um die Eindeutigkeit der Aufrufe wiederherzustellen.
Einige Interfaces haben die Annotation [Protocol]. Diese können, hier in im speziellen, alle verworfen werden.
Diverse Datentypen sind falsch übernommen worden, exemplarisch das *nint in “CDVUserAgentUtil.ReleaseLock“. Diese müssen wir zu einem normalen nint abändern.
Es gibt eine Codestelle, an der ein NSXMLParser als Returntype angegeben ist, dieser ist in Xamarin aufgrund von besseren Alternativen bewusst nicht implementiert worden, siehe Bugzilla. Wir ändern deshalb den Returntype einfach zu NSObject. Im gleichen Zug entfernen wir das INSXMLParserDelegate Interface vom CDVConfigParser.
Einige Interfaces haben eine Vererbungshierarchie, hier speziell „CDVConfigParser“ und „CDVCommandDelegateImpl“. Xamarin fügt im Buildprozess allen Interfaces das „I“ Präfix hinzu, also müssen wird dieses in der Vererbungshierachie mit angeben (exempl. „interface CDVCommandDelegateImpl : ICDVCommandDelegate“).
Integrieren der DLL in bestehende Apps
Nachdem wir unser Projekt nun von allen Fehlern bereinigt haben, können wir uns daran machen, eine dll zu buildn und diese in eine Xamarin-iOS App einzubinden. Die Konfiguration ist analog zu Cordova über eine „Config.xml“ . Die Startseite wird über <content src=“http://www.myWebsite.com/“/> gesetzt. Um die http-Funktionalität in iOS Apps zu aktivieren, muss man allerdings der Info.plist noch
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
hinzufügen, ansonsten hat man lediglich Support für https.
Ein Plugin erstellen und einbinden
Plugin erstellen
Wir haben aus den Anforderungen heraus nur ein Plugin - die Kamera - benötigt.
Bis zum dem Punkt an wir „Sharpie“ benutzt haben, um die ApiDefinition zu erzeugen, ist der Vorgang analog zu dem Erstellen von Cordova selbst.
Das Binding mit Sharpie war hier etwas komplizierter und nicht intuitiv, da wir auf diverse Probleme gestoßen sind, haben wir uns einiger Tricks angenommen.
Wir haben einen Order angelegt, der alle Header Dateien des Camera Plugin in einen Ordner (Cordova.Camera) kopiert, danach einen Ordner Cordova angelegt in Cordova.Camera und alle Header Dateien aus dem Cordova Projekt dorthin kopiert.
Der Grund hierfür ist, dass das Camera Plugin externe Dependencies auf das Cordova Projekt hat und diese sonst nicht findet. Daraus resultiert allerdings, dass wenn wir mit
sharpie bind -output Bindings.Cordova.Camera -sdk iphoneos10.3 *.h –c
die ApiDefinition vom Camera Plugin generieren, wir eine ApiDefinition mit einiges an Overhead bekommen. Nun vergleicht man die Objective C Dateien vom Plugin mit der Definition und löscht alles, was nicht explizit zum Plugin gehört.
Das Bereinigen und Modifizieren der ApiDefiniton und StructsAndEnums erfolgt nun wieder analog zu Cordova selbst.
Ein Plugin einbinden
Da der „CDVViewController“ bereits vom ViewController, iOS default erbt, kann man den CDVViewController problemlos in bestehende Hierarchien integrieren. Sollte man einen „BasisViewController“ haben kann man diesen nun vom „CDVViewController“ erben lassen.
In unserer nun Xamarin Hybrid-App können wir unsere Kamera im ViewDidLoad als Plugin registrieren mit
CDVCamera camera = new CDVCamera();
this.RegisterPluginWithClassName(camera, "CDVCamera");
Abschlusswort
Man kann Cordova also definitiv auf Xamarin portieren. Welchen Mehrwert man im Endeffekt aus diesen Schritten zieht, muss jeder für sich selbst entscheiden.
Es ist einiges an Arbeit die investiert werden muss, um diese Technologien zu verbinden sowie viele Details die beachtet werden müssen.
Dies sollte man sich gut überlegen, bevor man die Entscheidung fällt es für ein echtes Projekt zu verwenden.
Alle oben genannten Schritte muss man nach jedem Cordova Update wiederholen, um die Funktionalität vollständig zu erhalten.
Wir konnten genug Mehrwert aus der Arbeit, die wir investiert haben, gewinnen, um das zu rechtfertigen.
Einen Kommentar schreiben