Le point sur Xamarin
Dans cet article, nous allons présenter Xamarin, un outil en C# .NET permettant de faire du cross-platform sur mobile. Nous ferons un zoom plus important sur ce qu’il nous manquait : la réutilisation de librairies natives.
Qu'est-ce que Xamarin ?
Avant tout Xamarin est à la fois un produit mais aussi une société. Le produit permet de répondre à une problématique très courante qui est le développement cross-platform de manière unifiée.
En effet, Xamarin, permet de créer des applications natives sur les plateformes iOS, Android et Windows Phone. L’avantage de Xamarin réside en la réutilisation et le partage du code, réduisant le time to market.
Xamarin fourni aussi son propre environnement de développement Xamarin Studio.
Ce qu’il faut bien comprendre avec Xamarin, c’est le fait que les applications sont exécutées en natif. Toutes les APIS iOS ou Android sont disponibles via du code C#. Ceci est valable aussi donc pour les pushs, l’intégration des contacts, Bluetooth…
Oui, mais comment cela se passe en vrai ? Surtout au niveau du partage du code
Aujourd’hui, il y a deux manières de construire un projet Xamarin.
- La première est de créer une solution avec un projet Android, un projet iOS, et un projet commun (sous forme de Portable Class Library).
C’est dans cette Portable Class Library (librairie basée sur un sous-ensemble restreint de fonctionnalités .Net) que nous auront un maximum de code réutilisable. Dans cette librairie, nous mettrons nos « Models », notre couche d’accès aux services, … Depuis 2014, Xamarin supporte le pattern MVVM, bien connu des développeurs d’applications riches ou Modern avec du XAML. De fait, les ViewModels et Models pourront être partageables dans cette librairie commune. Pour plus d'informations sur le sujet, la documentation Xamarin est assez complète : http://developer.xamarin.com/guides/cross-platform/application_fundamentals/pcl/introduction_to_portable_class_libraries
- La seconde possibilité est de créer une solution avec un Shared Project
Dans un projet Shared Project, nous pouvons partager du code tout en y injectant des spécificités propres à la plateforme cible. Dans un Shared Project, chaque fichier est copié à la compilation dans les sous-projets (iOS, Android, Window Phone), et c'est grâce à des directives de compilations que l'on va pouvoir exécuter du code spécifique à chacune des plateformes. Il est aussi possible de partager des vues, Xamarin.Form s'appuie sur ce type de projet pour créer des applications très simples et avec peu de spécificités au niveau visuel. Ce sera le cas d'applications métiers, de prototypes ou d'applications ayant une identité visuelle forte nécessitant un rendu identique quelque soit la plateforme.
Un Shared Project est donc moins contraignant qu’une Portable Class Library (PCL) car cette dernière restreint l’accès au Framework .NET suivant les cibles de la PCL.
J'ai écrit des libraries sur chacune des plateformes, je ne veux pas tout ré-écrire pour Xamarin, est-ce possible ?
La réponse est oui. Xamarin, il y a 1 an, a apporté une réponse pour cette problématique : les projets de Bindings. Les projets de Bindings produisent une DLL (donc utilisable en C#) qui expose les API d'une librairie native iOS ou Android.
Ce type de projet est très intéressant pour les éditeurs de frameworks, car en très peu de temps et à moindre coût, ils vont pouvoir rendre disponible leur framework natif sur la plateforme Xamarin.
Le Project Binding Android
Xamarin.Android propose deux façons de consommer des librairies java :
- Utiliser le Java Native Interface (JNI) pour appeler directement les méthodes
- Créer un projet de type Java Binding Livrary qui exposera en C# les API de la bibliothèque. C’est cette méthode qui sera présentée dans cet article.
Le mécanisme du binding est implémenté en utilisant 2 mécanismes :
- le Manage Callable Wrappers (MCW) : le MCW est bridge JNI qui est utilisé à chaque appel vers la librairie java encapsulée par le code C# Xamarin
- l’Android Callable Wrappers (ACW) : réciproquement, le ACW est un bridge JNI qui est utilisé à chaque fois que du code Android runtime (ART) fait appel du « managed code »
Voici les étapes à suivre pour binder une librairie Java :
- Créer un projet de type Java Binding Library
- Ajouter le Jar que vous voulez encapsuler en tant que « EmbeddedJar » ainsi que les autres librairies dont celle-ci dépend en tant que « EmbeddedReferenceJar » (nous avons pris l'exemple des *.jar, mais il est aussi possible avec quelques efforts de Binder aussi des *.aar)
- Résoudre les quelques problèmes de binding dûs à la génération automatique (souvent il faut supprimer les nœuds qui ne doivent pas êtres exposés).
Pour cela, le projet génère automatiquement 3 fichiers xml de configuration :
- EnumFields.xml : il permet de binder les constantes Java de type int en enum C#
- EnumMethods.xml : il permet de modifier les types de retour et les paramètres Java de type int en enum C#
- MetaData.xml : il permet de modifier l’api qui sera généré (renommer un namespace, masquer des méthodes inutiles...)
Par exemple il est possible que nous voulions ne pas rendre disponibles certaines classes contenues dans le jar. Il suffit pour cela d’ajouter une ligne dans le fichier MetaData.xml ce qui suit :
<remove-node path="/api/package[@name='com.octo.android']/class[@name='MyClass']" />
Une fois votre projet de binding configuré et compilé, il suffit de l’ajouter en tant que référence à votre projet Android Xamarin. Il y aura un certain nombre de choses faire dans le fichier MetaData.xml, notamment pour renommer les différents paramètres d'une méthode. En effet, pour une méthode Java ayant pour signature :
public static void log (string logLevel, string message)
nous aurons en sortie :
public static void Log (string p0, string p1)
Pour l'utilisateur qui va intégrer le SDK, ce n'est pas très user friendly. Pour renommer les paramètres de méthode, il faut ajouter ces lignes :
<attr path="/api/package[@name='com.octo.android']/class[@name='MyClass']/method[@name='log']/parameter[@name='p0' and @type='java.lang.String']" name="name">logLevel</attr>
<attr path="/api/package[@name='com.octo.android']/class[@name='MyClass']/method[@name='log']/parameter[@name='p1' and @type='java.lang.String']" name="name">message</attr>
Pour plus d’informations concernant le binding d’une librairie Java (.jar), vous pouvez vous reporter à la doc Xamarin : http://developer.xamarin.com/guides/android/advanced_topics/java_integration_overview/binding_a_java_library_(.jar)/
Maintenant que vous avez généré une DLL encapsulant votre Jar, il suffit de l’ajouter à votre projet Xamarin Android. Vous avez désormais accès à votre librairie Java comme si cela avait été une DLL développée en C# !
Afin de correctement consommer votre nouvelle librairie, il est évidemment nécessaire de bien lire la documentation de celle-ci afin de s’assurer que votre projet Android Xamarin puisse faire les appels correctement et que les fichiers de configurations et clés de ressources sont bien présents dans ces fichiers.
Le Project Binding iOS
Il est nécessaire de développer ce projet sous Mac OS (ou d'en posséder un pour servir de build host, voir un peu plus bas). Pour cela, il faut au préalable installer la dernière version de XCode puis dans un second temps Xamarin pour Mac OS. L’inverse peut générer des problèmes lors de la compilation du projet.
Voici les étapes à suivre pour binder une librairie statique iOS :
- Créer un projet de type iOS Binding Project
- Ajouter la bibliothèque (.a) à votre projet
- Déclarer, dans le fichier ApiDefinition.cs, les interfaces à mapper
Suite à l’ajout de votre librairie iOS, il peut être nécessaire de modifier le fichier *.linkWith.cs afin d’importer les dépendances de votre librairie.
Exemple : ici j’importe lsqlite3.dylib qui utilisé par ma librairie iOS.
[assembly: LinkWith ("[myLib].a", LinkTarget.ArmV7 | LinkTarget.ArmV7s | LinkTarget.Simulator, ForceLoad = true, Frameworks = "CoreFoundation CoreGraphics CoreLocation Foundation QuartzCore UIKit", LinkerFlags = "-lsqlite3", IsCxx = true)]
Une fois les dépendances correctement importées, il faut maintenant s’attaquer aux interfaces que l’on souhaite rendre disponible dans votre nouvelle DLL. Pour cela, il faut donc se référer au .h et pour chaque interface Objective-C ajouter une interface C# au fichier ApiDefinition.cs
En partant de l'exemple d’interface suivant en Objective-C :
@interface MyClass : NSObject
+ (void)init;
- (NSString *)reverse:(NSString *)value;
- (NSString *)concat:(NSString *)a and:(NSString *)b;
+ (NSUUID *)currentId;
@property bool allOk;
@end
Le fichier ApiDefinition.cs qui mappe cette interface sera :
namespace myLib.iOS
{
[BaseType (typeof (NSObject))]
public interface MyClass
{
[Static, Export ("init")]
bool Init();
[Export ("reverse:")]
void Reverse(string value);
[Export("concat:and")]
string Contact([NullAllowed]string a, [NullAllowed]string b);
[Static, Export("currentId")]
string CurrentId { get; set; }
[Export("allOk")]
bool AllOk { get; set; }
}
}
Voici une liste d'astuces à se rappeler durant l'écriture du binding:
- L’interface que l’on souhaite mapper doit être décorée par un attribut BaseType prenant comme paramètre le type de l’interface (ici NSObject mais cela dépend de l’interface iOS).
- Les méthodes, variables et propriétés de l’interface à exporter sont décorées de l’attribut Export(nom de la méthode/propriété/variable)
- Certains types doivent être convertis en C# (voir la documentation Xamarin section Type mappings)
- Les paramètres sont requis doivent être précédés par [NullAllowed] sinon, le code généré par Xamarin testera la non nullité de ces paramètres et lèvera une exception avant même d’appeler la méthode de votre librairie iOS !
- Les méthodes sans paramètre n’ont besoin de fournir que le nom de celle-ci (côté iOS) comme paramètre de Export : Export ("init")
- Si celles-ci ont un seul paramètre, il est nécessaire de rajouter « : » après le nom Export ("reverse**:**")].
- En objective C, le nom d’une méthode peut être découpée par ses paramètres. Par exemple, le nom de la seconde méthode de l’exemple ci-dessus est « contat » et « and ».
- Pour mapper cette méthode, il est alors nécessaire d’ajouter avant les « : » chaque partie du nom de la méthode : Export("concat:and")
- Les méthodes, propriétés et variables de classe static doivent décorées avec l’attribut Static.
Pour plus d’information concernant le binding d’une librairie iOS (.a), vous pouvez vous reporter à la doc Xamarin : http://developer.xamarin.com/guides/ios/advanced_topics/binding_objective-c/binding_objc_libs/
Pour consommer votre librairie maintenant bindée, il suffit d’ajouter le projet de binding à votre projet Xamarin iOS pour accéder à tout ce que vous avez mappé manuellement dans le fichier ApiDefinition.cs.
Je ne suis pas à l'aise avec MacOS, est-ce que je peux rester sous Windows ?
Vous pouvez configurer un Mac afin de permettre la compilation depuis un Visual Studio sous Windows
En effet, Xamarin fourni un outil sous Mac OS permettant à un développeur sous Windows / Visual Studio de compiler un projet de binding et un projet Xamarin iOS depuis son poste de travail. Il s’appelle Xamarin.iOS Build Host. Une fois lancé, il suffit depuis le poste windows de référence l’ip du Mac et de renseigner le code PIN généré par Xamarin.iOS Build Host:
Conclusion
Grâce à Xamarin, les développeurs .NET habitués à Visual Studio et C# pourront créer des applications iOS (iPhone, iPad, Watch), Android (smartphone, tablette, wear) et bien entendu partager du code avec l’écosystème Microsoft (Phone, Tablette). Vous pouvez donc choisir de partir sur Xamarin pour un développement cross-platform si vos équipes connaissent déjà .NET. Cependant on ne s'invente pas développeurs Android et/ou iOS, il y aura donc un effort d'apprentissage non négligeable afin d'appréhender les spécificités de chacune des plateformes.