Création d'applications multiplateformes avec Xamarin : point de vue d'un développeur Android

Publié: 2022-03-11

Écrire du code une seule fois et l'utiliser sur plusieurs plates-formes a été le rêve de nombreux développeurs de logiciels. Bien que cela soit possible depuis un certain temps maintenant, cela s'est toujours fait au détriment de la maintenabilité, de la facilité des tests ou, pire encore, d'une mauvaise expérience utilisateur.

Le développement d'applications mobiles à l'aide du SDK natif est probablement le point de départ de tous les développeurs qui ont leurs racines dans le domaine du développement d'applications de bureau. Les langages de programmation deviendraient un obstacle pour certains : si quelqu'un avait de l'expérience dans le développement d'applications de bureau ou back-end Java, passer à une société de développement d'applications mobiles et travailler avec Android serait beaucoup plus facile que de commencer avec Objective-C à partir de zéro pour iOS.

J'ai toujours été sceptique quant au développement d'applications multiplateformes. Les frameworks basés sur JavaScript comme Sencha, Cordova, Titanium, etc. ne s'avèrent jamais être un choix judicieux lorsque les performances sont importantes. Le manque d'API et l'expérience utilisateur originale étaient une évidence avec ces frameworks.

Mais ensuite, je suis tombé sur Xamarin.

Développement multiplateforme avec Xamarin

Dans cet article, vous apprendrez comment utiliser Xamarin pour partager du code sur plusieurs plates-formes sans compromettre aucun des autres aspects du développement d'applications mobiles. L'article se concentrera sur Android et iOS en particulier, mais vous pouvez utiliser une approche similaire pour ajouter la prise en charge de toute autre plate-forme prise en charge par Xamarin.

Qu'est-ce que Xamarin ?

Xamarin est une plateforme de développement qui vous permet d'écrire des applications multiplateformes, mais natives, pour iOS, Android et Windows Phone en C# et .NET.

Xamarin fournit des liaisons C# aux API Android et iOS natives. Cela vous donne le pouvoir d'utiliser l'ensemble de l'interface utilisateur native d'Android et d'iOS, les notifications, les graphiques, les animations et d'autres fonctionnalités du téléphone, le tout à l'aide de C#.

Chaque nouvelle version d'Android et d'iOS est associée à Xamarin, avec une nouvelle version qui inclut des liaisons pour leurs nouvelles API.

Le port Xamarin de .NET inclut des fonctionnalités telles que les types de données, les génériques, la récupération de place, la requête intégrée au langage (LINQ), les modèles de programmation asynchrone, les délégués et un sous-ensemble de Windows Communication Foundation (WCF). Les bibliothèques sont gérées avec un linger pour inclure uniquement les composants référencés.

Xamarin.Forms est une couche au-dessus des autres liaisons d'interface utilisateur et de l'API Windows Phone, qui fournit une bibliothèque d'interface utilisateur entièrement multiplateforme.

La portée de Xamarin

Rédaction d'applications multiplateformes

Pour écrire des applications multiplateformes avec Xamarin, les développeurs doivent choisir l'un des deux types de projets disponibles :

  • Bibliothèque de classes portable (PCL)
  • Projet partagé

Le PCL vous permet d'écrire du code qui peut être partagé entre plusieurs plates-formes, mais avec une limitation. Étant donné que toutes les API .NET ne sont pas disponibles sur toutes les plates-formes, avec un projet PCL, vous limiterez son exécution sur les plates-formes pour lesquelles il est ciblé.

Connexions et limites de Xamarin

Le tableau ci-dessous montre quelles API sont disponibles sur quelles plates-formes :

Caractéristique .NET Framework Applications du magasin Windows Silverlight Téléphone Windows XamarinName
Cœur Oui Oui Oui Oui Oui
LINQ Oui Oui Oui Oui Oui
IQueryable Oui Oui Oui 7.5+ Oui
Sérialisation Oui Oui Oui Oui Oui
Annotations de données 4.0.3+ Oui Oui Oui Oui

Pendant le processus de construction, un PCL est compilé dans des DLL distinctes et chargé par Mono pendant l'exécution. Une implémentation différente de la même interface peut être fournie pendant l'exécution.

D'autre part, les projets partagés vous donnent plus de contrôle en vous permettant d'écrire du code spécifique à la plate-forme pour chaque plate-forme que vous souhaitez prendre en charge. Le code d'un projet partagé peut contenir des directives de compilateur qui activeront ou désactiveront des sections de code en fonction du projet d'application qui utilise le code.

Contrairement à un PCL, un projet partagé ne produit aucune DLL. Le code est inclus directement dans le projet final.

Structurer votre code multiplateforme avec MvvmCross

Le code réutilisable peut faire gagner du temps et de l'argent aux équipes de développement. Cependant, un code bien structuré facilite grandement la vie des développeurs. Personne n'apprécie plus un code bien écrit et sans bogue que les développeurs.

Xamarin fournit à lui seul un mécanisme qui facilite grandement l'écriture de code multiplateforme réutilisable.

Les développeurs mobiles connaissent les scénarios dans lesquels ils doivent écrire la même logique deux fois ou plus afin de prendre en charge iOS, Android et d'autres plates-formes. Mais avec Xamarin, comme expliqué dans le chapitre précédent, il est facile de réutiliser le code écrit pour une plate-forme pour d'autres plates-formes également.

Où MvvmCross entre-t-il alors en place ?

MvvmCross, comme son nom l'indique, permet d'utiliser le modèle MVVM dans les applications Xamarin. Il est livré avec un tas de bibliothèques, d'API et d'utilitaires qui sont vraiment pratiques dans le développement d'applications multiplateformes.

MvvmCross peut réduire considérablement la quantité de code passe-partout que vous auriez écrit (parfois plusieurs fois dans différentes langues) dans toute autre approche du développement d'applications.

Structure d'une solution MvvmCross

La communauté MvvmCross recommande une manière assez simple et efficace de structurer une solution MvvmCross :

 <ProjectName>.Core <ProjectName>.UI.Droid <ProjectName>.UI.iOS

Le projet Core dans une solution MvvmCross est lié au code réutilisable. Le projet Core est un projet Xamarin PCL dont l'objectif principal est la réutilisabilité.

Tout code écrit en Core doit être indépendant de la plate-forme de la manière la plus complète possible. Il ne doit contenir qu'une logique pouvant être réutilisée sur toutes les plates-formes. Le projet Core ne doit utiliser aucune API Android ou iOS ni accéder à quoi que ce soit de spécifique à aucune plate-forme.

La couche de logique métier, la couche de données et la communication back-end sont toutes des candidats parfaits pour être incluses dans le projet Core. La navigation dans la hiérarchie des vues (activités, fragments, etc.) sera réalisée dans le Core.

Avant de continuer, il est nécessaire de comprendre un modèle de conception architecturale qui est crucial pour comprendre MvvmCross et son fonctionnement. Comme son nom l'indique, MvvmCross dépend fortement du modèle MVVM.

MVVM est un modèle de conception architecturale qui facilite la séparation de l'interface utilisateur graphique de la logique métier et des données back-end.

Comment ce modèle est-il utilisé dans MvvmCross ?

Eh bien, puisque nous voulons atteindre une réutilisabilité élevée de notre code, nous voulons en avoir autant que possible dans notre Core, qui est un projet PCL. Étant donné que les vues sont la seule partie du code qui diffère d'une plate-forme à l'autre, nous ne pouvons pas les réutiliser d'une plate-forme à l'autre. Cette partie est implémentée dans les projets liés à la plateforme.

Structure MvvmCross

MvvmCross nous donne la possibilité d'orchestrer la navigation de l'application depuis le Core à l'aide de ViewModels.

Avec les bases et les détails techniques à l'écart, commençons avec Xamarin en créant notre propre projet MvvmCross Core :

Création d'un projet MvvmCross Core

Ouvrez Xamarin Studio et créez une solution nommée ToptalExampleSolution :

Créer la solution

Puisque nous créons un projet Core, c'est une bonne idée de s'en tenir à la convention de nommage. Assurez-vous que le suffixe Core est ajouté au nom du projet.

Afin d'obtenir le support de MvvmCross, il est nécessaire d'ajouter des bibliothèques MvvmCross à notre projet. Pour ajouter que nous pouvons utiliser la prise en charge intégrée de NuGet dans Xamarin Studio.

Pour ajouter une bibliothèque, cliquez avec le bouton droit sur le dossier Packages et sélectionnez l'option Ajouter des packages… .

Dans le champ de recherche, nous pouvons rechercher MvvmCross, qui va filtrer les résultats liés à MvvmCross comme indiqué ci-dessous :

Filtrer les résultats

Cliquer sur le bouton Ajouter un paquet l'ajoutera au projet.

Avec MvvmCross ajouté à notre projet, nous sommes prêts à écrire notre code Core.

Définissons notre premier ViewModel. Pour en créer un, créez une hiérarchie de dossiers comme suit :

Hiérarchie recommandée des dossiers

Voici en quoi consiste chacun des dossiers :

  • Modèles : modèles de domaine qui représentent le contenu de l'état réel
  • Services : un dossier contenant notre service (logique métier, base de données, etc.)
  • ViewModel : la façon dont nous communiquons avec nos modèles

Notre premier ViewModel s'appelle FirstViewModel.cs

 public class FirstViewModel : MvxViewModel { private string _firstName; private string _lastName; private string _fullName; public string FirstName { get { return _firstName; } set { _lastName = value; RaisePropertyChanged(); } } public string LastName { get { return _lastName; } set { _lastName = value; RaisePropertyChanged(); } } public string FullName { get { return _fullName; } set { _fullName = value; RaisePropertyChanged(); } } public IMvxCommand ConcatNameCommand { get { return new MvxCommand(() => { FullName = $"{FirstName} {LastName}"; }); } public IMvxCommand NavigateToSecondViewModelCommand { get { return new MvxCommand(() => { ShowViewModel<SecondViewModel>(); }); } } }

Maintenant que nous avons notre premier ViewModel, nous pouvons créer notre première vue et lier les choses ensemble.

Interface utilisateur Android

Pour afficher le contenu du ViewModel, nous devons créer une interface utilisateur.

La première étape de la création d'une interface utilisateur Android consiste à créer un projet Android dans la solution actuelle. Pour ce faire, faites un clic droit sur le nom de la solution et sélectionnez Ajouter -> Ajouter un nouveau projet… . Dans l'assistant, sélectionnez l'application Android et assurez-vous de nommer votre projet ToptalExample.UI.Droid .

Comme décrit précédemment, nous devons maintenant ajouter des dépendances MvvmCross pour Android. Pour ce faire, suivez les mêmes étapes que pour le projet Core pour ajouter des dépendances NuGet.

Après avoir ajouté les dépendances MvvmCross, il est nécessaire d'ajouter une référence à notre projet Core afin que nous puissions utiliser notre code qui y est écrit. Pour ajouter une référence au projet PCL, cliquez avec le bouton droit sur le dossier Références et sélectionnez l'option Modifier les références… . Dans l'onglet Projets, sélectionnez le projet Core précédemment créé et cliquez sur OK.

Ajout d'une référence au projet PCL

La partie suivante peut être un peu difficile à comprendre.

Nous devons maintenant dire à MvvmCross comment il doit configurer notre application. Pour ce faire, nous devons créer une classe Setup :

 namespace ToptalExample.UI.Droid { public class Setup : MvxAndroidSetup { public Setup(Context context) : base(context) { } protected override IMvxApplication CreateApp() { return new Core.App(); } } }

Comme on peut le voir à partir de la classe, nous disons à MvvmCross de CreateApp en fonction de l'implémentation Core.App , qui est une classe définie dans Core et illustrée ci-dessous :

 public class App : MvxApplication { public override void Initialize() { RegisterAppStart(new AppStart()); } } public class AppStart : MvxNavigatingObject, IMvxAppStart { public void Start(object hint = null) { ShowViewModel<FirstViewModel>(); } }

Dans la classe App , nous créons une instance de AppStart , qui va afficher notre premier ViewModel.

Il ne reste plus qu'à créer un fichier de mise en page Android qui sera lié par MvvmCross :

 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:andro xmlns:local="http://schemas.android.com/apk/res-auto" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <EditText android:layout_width="match_parent" android:layout_height="match_parent" local:MvxBind="Text FirstName" /> <EditText android:layout_width="match_parent" android:layout_height="match_parent" local:MvxBind="Text LastName" /> <TextView android:layout_width="match_parent" android:layout_height="match_parent" local:MvxBind="Text FullName" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" local:MvxBind="Click ConcatNameCommand" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" local:MvxBind="Click NavigateToSecondViewModelCommand" /> </LinearLayout>

Dans le fichier de mise en page, nous avons des liaisons qui sont automatiquement résolues par MvvmCross. Pour EditText , nous créons une liaison pour la propriété Text, qui sera une liaison bidirectionnelle. Toute modification invoquée du côté ViewModel sera automatiquement répercutée sur la vue et vice-versa.

La classe View peut être une activité ou un fragment. Pour plus de simplicité, nous utilisons une activité qui charge la mise en page donnée :

 [Activity(Label = "ToptalExample.UI.Droid", Theme = "@style/Theme.AppCompat", MainLauncher = true, Icon = "@mipmap/icon")] public class MainActivity : MvxAppCompatActivity<FirstViewModel> { protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); SetContentView(Resource.Layout.Main); } }

Pour le premier bouton, nous avons une liaison de commande, ce qui signifie que lorsque nous cliquons sur le bouton, MvvmCross invoquera ContactNameCommand à partir du ViewModel.

Pour le deuxième bouton, nous allons montrer un autre ViewModel.

Interface utilisateur iOS

La création d'un projet iOS n'est pas vraiment différente de la création d'un projet Android. Vous devez suivre des étapes similaires pour ajouter un nouveau projet, mais cette fois, au lieu d'Android, créez simplement un projet iOS. Assurez-vous simplement que la convention de dénomination est cohérente.

Après avoir ajouté le projet iOS, vous devez ajouter des dépendances pour MvvmCross iOS. Les étapes sont absolument les mêmes que pour Core et Android (clic droit sur Références dans votre projet iOS et cliquez sur Ajouter des références… ).

Maintenant, comme nous l'avons fait pour Android, il est nécessaire de créer une classe Setup , qui va indiquer à MvvmCross comment configurer notre application.

 public class Setup : MvxIosSetup { public Setup(MvxApplicationDelegate appDelegate, IMvxIosViewPresenter presenter) : base(appDelegate, presenter) { } protected override MvvmCross.Core.ViewModels.IMvxApplication CreateApp() { return new App(); } }

Notez que la classe Setup étend maintenant MvxIosSetup et, pour Android, elle étendait MvxAndroidSetup .

Un ajout ici est que nous devons changer notre classe AppDelegate .

AppDelegate sur iOS est responsable du lancement de l'interface utilisateur, nous devons donc dire comment les vues vont être présentées sur iOS. Vous pouvez en savoir plus sur les présentateurs ici.

 [Register("AppDelegate")] public class AppDelegate : MvxApplicationDelegate { // class-level declarations public override UIWindow Window { get; set; } public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions) { Window = new UIWindow(UIScreen.MainScreen.Bounds); var presenter = new MvxIosViewPresenter(this, Window); var setup = new Setup(this, presenter); setup.Initialize(); var startup = Mvx.Resolve<IMvxAppStart>(); startup.Start(); Window.MakeKeyAndVisible(); return true; } }

Afin de présenter notre VIewModel, nous devons créer une vue. Dans ce cas, créons un ViewController en cliquant avec le bouton droit sur le projet et en sélectionnant Ajouter -> Nouveau fichier et sélectionnez ViewController dans la section iOS, que nous allons nommer FirstViewController.

Xamarin crée trois fichiers dans lesquels nous allons définir ce que seront nos liaisons. Contrairement à Android, pour iOS, nous devons définir nos liaisons d'une manière différente, par le biais de code (bien que nous puissions également le faire sur Android et, dans certains cas, il est nécessaire de le faire).

Lorsqu'il est nécessaire de naviguer entre les vues, cela se fait via le ViewModel. Dans la commande NavigateToSecondViewModelCommand , la méthode ShowViewModel<SecondViewModel>() trouvera la vue appropriée et y naviguera.

Mais comment MVVMCross sait-il quelle vue charger ?

Il n'y a pas de magie là-dedans. Lorsque nous créons une vue pour Android (Activity ou Fragment), nous étendons l'une des classes de base avec des paramètres de type ( MvxAppCompatActivity<VM> ). Lorsque nous appelons ShowViewMolel<VM> , MvvmCross recherche une View qui étend la classe Activity ou Fragment avec des paramètres de type VM . C'est pourquoi vous n'êtes pas autorisé à avoir deux classes de vue pour le même ViewModel.

Inversion de contrôle

Étant donné que Xamarin fournit simplement des wrappers C # autour des API natives, il ne fournit aucune forme de mécanisme d'injection de dépendance (DI) ou d'inversion de contrôle (IoC).

Sans injection de dépendances au moment de l'exécution ou injection au moment de la compilation, il n'est pas facile de créer des composants faiblement couplés, réutilisables, testables et facilement maintenables. L'idée d'IoC et DI est connue depuis très longtemps ; des détails sur IoC peuvent être trouvés dans de nombreux articles en ligne. Vous pouvez en savoir plus sur ces modèles dans l'article d'introduction de Martin Fowler.

IoC est disponible depuis les premières versions de MvvmCrosses et permet l'injection de dépendances au moment de l'exécution lorsque l'application démarre et chaque fois qu'elles sont nécessaires.

Afin d'obtenir des composants faiblement couplés, nous ne devrions jamais exiger d'implémentations concrètes de classes. Exiger des implémentations concrètes limite la possibilité de modifier le comportement des implémentations pendant l'exécution (vous ne pouvez pas le remplacer par une autre implémentation). Il est difficile de tester ces composants.

Pour cette raison, nous allons déclarer une interface pour laquelle nous allons avoir une implémentation concrète.

 public interface IPasswordGeneratorService { string Generate(int length); }

Et mise en œuvre :

 public class PasswordGeneratorService : IPasswordGeneratorService { public string Generate(int length) { var val; var res = new StringBuilder(); var rnd = new Random(); while (0 < length--) { res.Append(valid[rnd.Next(valid.Length)]); } return res.ToString(); } }

Notre ViewModel peut maintenant exiger une instance de l'interface IPasswordGenerationService , que nous sommes chargés de fournir.

Pour que MvvmCross injecte une implémentation PasswordGeneratorService lors de l'exécution, nous devons indiquer à MvvmCross quelle implémentation utiliser. Si nous voulons utiliser une implémentation pour les deux plates-formes, nous pouvons enregistrer les implémentations dans App.cs , après l'enregistrement de l'application :

 public override void Initialize() { RegisterAppStart(new AppStart()); Mvx.LazyConstructAndRegisterSingleton<IPasswordGeneratorService, PasswordGeneratorService>(); }

L'appel ci-dessus à la méthode statique LazyConstructAndRegisterSingleton<TInterface, TType> enregistre l'implémentation à injecter. Cette méthode enregistre l'implémentation appropriée mais ne crée pas d'objet.

L'objet est créé uniquement lorsqu'il est requis et une seule fois puisqu'il est enregistré en tant que singleton.

Si nous voulons créer un objet singleton tout de suite, cela peut être réalisé en appelant Mvx.RegisterSingleton<TInterface>() .

Il y a des cas où nous ne voulons pas avoir que des singletons dans notre application. Notre objet peut ne pas être thread-safe ou il peut y avoir une autre raison pour laquelle nous voulons toujours avoir une nouvelle instance. Si tel est le cas, MvvmCross fournit la méthode Mvx.RegisterType<TInterface,TType>() , qui peut être utilisée pour enregistrer l'implémentation de manière à instancier une nouvelle instance chaque fois que cela est nécessaire.

Si vous devez fournir des implémentations concrètes distinctes pour chaque plate-forme, vous pouvez toujours le faire dans les projets spécifiques à la plate-forme :

 public class DroidPasswodGeneratorService : IPasswordGeneratorService { public string Generate(int length) { return "DroidPasswordGenerator"; } }

Et l'enregistrement de notre implémentation se fait dans la classe Setup.cs sous le projet Droid :

 protected override void InitializePlatformServices() { base.InitializePlatformServices(); Mvx.LazyConstructAndRegisterSingleton<IPasswordGeneratorService, DroidPasswodGeneratorService>(); }

Après l'initialisation du code PCL, MvvmCross appellera InitializePlatformServices et enregistrera nos implémentations de services spécifiques à la plate-forme.

Lorsque nous enregistrons plusieurs implémentations singleton, MvvmCross n'utilisera que l'implémentation qui a été enregistrée en dernier. Toutes les autres inscriptions seront rejetées.

Créez des applications multiplateformes avec Xamarin

Dans cet article, vous avez vu comment Xamarin vous permet de partager du code sur différentes plates-formes tout en conservant la sensation et les performances natives des applications.

MvvmCross offre une autre couche d'abstraction améliorant encore l'expérience de création d'applications multiplateformes avec Xamarin. Le modèle MVVM fournit un moyen de créer des flux de navigation et d'interaction utilisateur communs à toutes les plates-formes, ce qui limite la quantité de code spécifique à la plate-forme que vous devez écrire aux seules vues.

J'espère que cet article vous a donné une raison de jeter un coup d'œil à Xamarin et vous a motivé à créer votre prochaine application multiplateforme avec.