Après la brève introduction à PRISM pour Xamarin.Forms (Cf. partie 1), il est temps de voir comment cela fonctionne en pratique…
Le template
S’il est bien entendu possible d’ajouter PRISM à tout nouveau projet ou même tout projet existant il peut être plus pratique de partir d’un template lorsqu’on a fait le choix d’utiliser ce framework.
Dans les extensions de Visual Studio il faut chercher et installer le package suivant (le premier surligné en bleu) :
Cette extension fournie plusieurs templates dont un pour les Xamarin.Forms. Elle a été conçue par le programmeur principal de PRISM (Brian Lagunas), on peut la trouver aussi sur le site Visual Studio Gallery.
Créer un nouveau projet avec PRISM
La création d’un nouveau projet utilisant PRISM une fois les templates installés est très simple puisqu’il suffit de choisir dans la section Prism le modèle WPF ou Xamarin.Forms.
On remarquera qu’en réalité il existe plusieurs templates qui diffèrent par le conteur IOC qu’ils installent : Autofac, DryIoc, Ninject, Unity. Dans l’exemple ci-dessous je vais sélectionner Unity qui est le framework de l’équipe Patterns & Practices de Microsoft.
Une fois ce dialogue validé un second va s’afficher pour vous permettre de choisir les plateformes à intégrer, ce qui est bien pratique :
Visual Studio crée ensuite les projets demandés ainsi que le projet PCL portable qui contiendra l’essentiel du code :
Le projet partagé contient par défaut une vue principale et un ViewModel pour cette dernière.
Si on fait un tour du côté des packages Nuget installé pour la solution on trouve notamment ceux-ci :
Prism.Core est le noyau portable de PRISM, c’est le même pour toutes les plateformes même WPF. Prism.Forms est la partie spécifique à la plateforme en cours, ici les Xamarin.Forms, Unity est le framework IOC, Prism.Unity.Forms fait le lien entre Unity et Prism et le CommonServiceLocator est un localisateur de service standard de chez Microsoft (il crée une interface séparant le code de l’application du conteneur IOC ce qui permet d’en changer plus facilement selon le principe du découplage…).
Le choix du conteneur IOC
Quel conteneur IOC choisir et quelle importance / incidence cela peut-il avoir ?
Voici une question très importante qui mérite un développement séparé tant le sujet est crucial !
Mais cela tombe bien puisque je l’ai déjà largement abordé notamment dans un papier du 14 avril 2015 qui avait le bon gout de traiter de l’injection de dépendances et de conteneurs IOC dans le contexte des Xamarin.Forms. Cet article propose notamment une grille comparative entre les principaux conteneurs IOC dont les quatre évoqués ici qui fonctionnent avec PRISM. Ne vous laissez pas abuser par le début du titre qui peut faire croire que tout cela est vieux, d’abord comme vous le constaterez la problématique n’est pas récente mais reste à propos et ensuite on sait que Surface Phone va bientôt venir remplacer Windows Phone… Alors tout en étant en retard le titre pourrait être trop en avance :-)
Windows Phone, Android et iOS & Injection de dépendances et conteneurs IOC
A lire absolument donc…
La classe App
Par rapport aux anciennes versions de PRISM l’une des différences se trouve dans l’utilisation de la classe App (fichiers App.xaml et App.xaml.cs). Originellement Prism utilisait une classe Bootstrapper pour se paramétrer et s’initialiser. Mais comme une telle classe systématiquement exécutée en début d’application existe sous Xamarin.Forms (la classe App) il n’est plus nécessaire d’en ajouter une autre. C’est donc la classe App “naturelle” qui est utilisée pour l’initialisation.
Par défaut App hérite de Application, dans le cas de PRISM l’héritage est modifié pour descendre de PrismApplication.
L’intérêt d’utiliser le template est que cette adaptation de App est déjà effectuée, sinon il faut le faire soi-même si on ajoute PRISM à un projet existant.
On remarque que la classe App supporte de nouvelles fonctionnalités. Elle possède notamment un nouveau constructeur qui prend en paramètre un IPlatformInitializer. Elle surchage la méthode OnItinialized qui initialise l’infrastructure Xamarin.Forms mais qui se charge aussi de naviguer vers la première page via un mécanisme propre à PRISM sur lequel nous reviendrons. Enfin on découvre aussi une méthode RegisterTypes qui permet de centraliser les dépendances injectée dans le conteneur IOC.
Le paramètre IPlatformInitializer est à nul par défaut. Mais il peut être utilisé dans le cas où on le besoin d’enregistrer des classes particulières dans le conteneur IOC s’impose et ce uniquement dans certains projets natifs. D’ailleurs si on regarde l’un de ces projets, Android par exemple, on remarque la présence d’une classe supplémentaire supportant l’interface et s’appelant AndroidInitializer. Ce nom change sous chaque plateforme pour bien marquer sa spécificité native.
Par défaut la classe fournie ne fait rien mais elle propose une méthode RegisterType qui peut ainsi être utilisée pour injecter des dépendances liées à une plateforme spécifique.
Connecter Vues et ViewModels
L’un des problème à régler avec MVVM est d’arriver à connecter les ViewModels à leurs Vues tout en restant le plus découplé possible, c’est à dire sans que les uns et les autres n’aient à s’appeler directement.
La plupart des frameworks MVVM proposent leur propre façon d’effectuer cette liaison.
Si on prend Xamarin.Forms de base, la façon de faire la plus souvent utilisée est un couplage fort : la Vue crée dans son constructeur le ViewModel qu’elle assigne à son Binding Context. C’est clair, net, simple, mais très couplé donc pas très bon…
Si on prend Mvvm Light on retrouve selon les versions l’implémentation d’une classe ViewModelLocator qui crée une indirection assurant le découplage et permettant de déclarer le lien directement en XAML. C’est nettement mieux même si le ViewModelLocator et son implémentation peuvent être vus comme un peu tortueux et ne faisant pas partie réellement du framework. Mais c’est un bon début.
PRISM propose une véritable approche automatisée qui évite la présence d’une classe à écrire soi-même ou un couplage fort. C’est nettement mieux.
Cette approche se base sur des conventions simples qu’il suffit de suivre mais qu’on peut aussi modifier si on le désire :
- La page XAML de la Vue doit être stockée dans le répertoire nommé Views
- Le ViewModel doit être stocké dans le répertoire nommé ViewModels
- Il doit porter le même nom que la Vue avec le suffixe ViewModel
C’est ce qu’on peut voir dans la capture écran montrant le projet après sa création (plus haut).
Ainsi la Vue MainPage aura un ViewModel s’appelant MainPageViewModel. Chacun étant stocké dans son répertoire (View et ViewModels).
Là encore l’avantage du template PRISM est de fournir l’infrastructure qui permet de débuter le codage de l’application immédiatement. Si on ajoute PRISM manuellement ou après coup il faudra modifier le projet pour qu’il réponde aux exigences de nommage et de localisation des objets.
Pour que l’automatisme proposé par PRISM fonctionne il faut aussi que les pages soient enregistrées dans le conteneur IOC. C’est notamment le rôle de la méthode RegisterType() qu’on peut voir dans la classe App (voir plus haut la capture). L’appel à la méthode RegisterTypeForNavigation<T> autorise l’enregistrement des vues. Cela marque d’ailleurs une différence entre PRISM et d’autres frameworks Mvvm. En général les ViewModels sont enregistrés, en plus des Vues parfois et même des liens entre les uns et les autres. PRISM simplifie tout cela, les liens sont créés virtuellement par le respect des conventions de nommage et de localisation expliquées plus haut, il n’y a donc rien à préciser, et seules les Vues sont données au conteneur IOC, PRISM s’occupe de retrouve les ViewModels correspondants et de les enregistrer automatiquement.
Dans certains cas on peut vouloir décrocher de l’automatisme reliant Vue et ViewModel selon leur nom, dans ce cas il suffit d’utiliser la variante RegisterTypeForNavigation<T,Y> qui permet de passer le type de la Vue et celui de son ViewModel, aussi simplement que cela…
Tout cela aura forcément un impact sur la navigation, sujet dont je parlerai dans un prochain papier car il réclame d’être étudié attentivement.
La Vue
PRISM pour Xamarin.Forms ne réclame pas grand chose…
Notamment le code C# lié à la Vue n’a pas â être modifié ni n’a besoin d’hériter d’une classe mère particulière. Et si l’on regarde le code XAML de la Vue on n’y trouve que deux lignes liées à la présence de PRISM :
- D’une part la déclaration du namespace
- D’autre part l’utilisation de ce dernier pour définir la propriété AutowireViewModel à True.
C’est cette propriété qui prend soin de connecter les Vues à leurs ViewModels de façon automatique en utilisant le BindingContext des premières.
Toutefois depuis la version 6.2 de PRISM la présence de cette déclaration n’est même plus nécessaire, sauf si on veut justement stopper l’automatisme de connexion, ce qui est très rare.
Le ViewModel
Le ViewModel est un élément central du pattern MVVM. Au départ cela peut être juste une classe tout à fait normale, c’est ce qu’on voit dans les exemples les plus simples de MVVM avec l’outillage de base des Xamarin.Forms.
Néanmoins un ViewModel a le plus souvent et au minimum besoin d’implémenter INPC (l’interface INotifyPropertyChanged) pour que les changements de valeur des propriétés soient propagés à la Vue via le Binding XAML.
Mais dans le contexte MVVM d’autres besoins peuvent apparaître comme le support d’une messagerie, de services de dialogue, etc.
De fait la plupart des frameworks MVVM propose une classe mère dont les ViewModels doivent hérités pour bénéficier de tous ces petits avantages.
PRISM n’échappe pas à la règle tout en restant extrêmement simple.
La classe mère fournie par PRISM s’appelle BindableBase. Son nom nous éclaire sur son rôle : rendre le binding opérationnel, donc supporter INPC… Cela se fait en simplifiant les setters par la présence de la méthode SetProperty qui s’occupe de vérifier que la valeur est bien différente de l’actuelle, de faire l’assignation et de déclencher INPC. On retrouve ici un mécanisme qui a été emprunté à Mvvm Light de façon totalement ouverte d’ailleurs. De toute façon tous ces frameworks sont gratuits et Open Source et ils peuvent se faire des emprunts sans souci.
On remarque aussi la présence du support de l’interface INavigationAware qui implique celle des trois méthodes dédiées à la navigation.
C’est un ajout très intéressant des dernières releases de la version 6 car la navigation est un casse-tête récurrent et tout bon framework se doit d’y apporter une solution simple. C’est un point sur lequel Mvvm Light achoppe encore hélas. PRISM permet ainsi à un ViewModel de savoir quand la navigation l’active (OnNavigatedTo, OnNavigatingTo), quand il va être désactivé par la navigation (OnNavigatingFrom). C’est une façon de nommer ces méthodes assez courante même si personnellement j’ai toujours un peu de mal à m’y retrouver (To et From dépendent du point de vue, de l’extérieur de la classe ou de l’intérieur ce qui est est source de confusion inutile à mon sens même s’il faut admettre le caractère logique de se placer depuis le point de vue de la classe. C’est logique mais une fois qu’on le sait…).
Le service de navigation que j’aborderai plus tard en détail offre bien entendu d’autres subtilités essentielles !
Conclusion
PRISM est un framework qui a toujours été intéressant mais qui dans ces dernières versions devient en plus parfaitement limpide tout en restant plus complet que la référence qu’est Mvvm Light. Certainement un peu moins “light” que ce dernier en terme de code, tout en étant parfaitement adapté aux unités mobiles, PRISM offre beaucoup de choses que Mvvm Light ne sait pas faire (automatisme de connexion Vues/ViewModel, service de navigation etc).
Ce framework est très intelligemment fait et se situe à mi-chemin entre MvvmLight et Caliburn ce qui en fait à mon avis un excellent choix pour écrire des applications supportant MVVM.
Toutefois je n’ai fait ici que de gratter la surface des possibilités de PRISM, nous verrons dans de prochains articles que sa puissance et sa simplicité d’utilisation valent le détour !
Stay Tuned !