[3/4] Architecture Cross-Plateformes Xamarin : couche présentation

Auteur du billet de blog : Rudy Spano - Neotech Solutions

Rudy Spano

Consultant .Net
  Publié le mercredi 1 février 2017

Expert technique .Net passionné par tout ce qui est innovant en environnement Microsoft. De la mobilité, du client lourd (Xaml Applications) mais aussi une petite pointe de Web...

Contexte

 

La librairie BookStore.Forms est notre couche de présentation Cross-Platformes respectant notamment le pattern MVVM.

On y trouve différents namespaces détaillés ci-dessous.

 

Namespace Views 

Ce namespace contient les fichiers XAML des éléments de notre interface ainsi que leur code-behind (xaml.cs).

 

++ Définir tant que possible l’UX (« User Experience ») dans le fichier XAML. Interface, Interactions (Events, Behaviors), Triggers, Storyboards, Resources, …

?? Comme cas particulier, il arrive que XAML ne nous permette pas ou pas simplement d’effectuer une opération sur l’interface. Si ce code ne concerne que l’interface, sa place légitime est dans le xaml.cs. Construction dynamique compliquée,  « Gestures » spécifiques (Manipulations), lancement storyboard, …

++ Isoler des parties de l’interface dans des UserControls si nécessaire pour des fins de mutualisation ou tout simplement de découpage.

++ Utiliser le DataBinding pour limiter le couplage avec les autres couches applicatives.

++ Utiliser les modes de DataBinding appropriés pour les propriétés liées : OneTime (non implémenté en Xamarin Forms), OneWay, OneWayToSource, TwoWay.

++ Définir le DataContext de la vue (//ViewModel) en XAML si possible. Cette dépendance doit être évidente lorsque l’on parcoure le code de la vue. Cela ajoute aussi d’autres avantages liées à l’auto complétion et au designer.

?? Récupérer l’instance du ViewModel à associer via une classe ViewModelLocator que vous retrouverez dans la plupart des Toolkits MVVM.

?? Considérer créer des données de Mock pour le Designer via l’utilisation de DataContext de test (code en dur, fichier XML, …). Ceci simplifie grandement les développements liés à l’interface.

++ Utiliser les styles pour définir un ensemble de propriétés. A partir du moment où vous définissez N propriétés pour un control XAML, l’utilisation des styles offre des avantages cruciaux : amélioration de la lisibilité du code XAML, mutualisation des propriétés pour assurer la cohérence de l’UI, évolutivité.

++ Ordonner les propriétés des contrôles XAML de façon cohérente. Nom du control (si nécessaire) – Positionnement/Alignement – Style/Template – Autres propriétés sans DataBinding – Propriétés avec DataBinding.

?? Créer des sous-namespaces de vues en les groupant logiquement si nécessaire.

-- Evitez de définir des tailles fixes. Votre UI doit pouvoir s’adapter à différents « Form Factors ». Même si vous ne ciblez qu’un seul device, cela vous évitera de faire des calculs scientifiques pour déterminer certaines tailles qui dégradent l’évolutivité de l’application.

++ Gérer les spécificités de vos vues dépendantes de la plateforme d’exécution via l’utilisation de la propriété OnPlatform de Xamarin Forms.

 

Namespace ViewModels 

Les modèles de vues (« View Models ») ont pour but d’assurer la communication bidirectionnelle entre les vues et le code métier de l’application. Ils sont utilisés comme BindingContext/DataContext des vues dans les applications à base de DataBinding respectant MVVM. Dans notre exemple BookBasketViewModel est lié à la vue BookBasketView par exemple.

 

++ Définir des propriétés facilitant le DataBinding et l’affichage sans se « limiter » à une simple retranscription des propriétés des objets métiers récupérés. C’est l’enjeu principal du ViewModel. Notre objet Book a 3 propriétés ReleaseDate, IsOutOfStock, IsHidden => BookViewModel propose une propriété simple et unique IsAvailable utilisée par l’interface.

++ Notifier l’interface (implémentation de INotifyPropertyChanged) qu’aux moments opportuns lors du changement de valeur pour les propriétés avec DataBinding.

++ Exposer les propriétés de type Collection via l’utilisation de la classe ObservableCollection.

++ Définir des commandes (implémentation de ICommand) avec DataBinding sur les vues afin de gérer les actions proposées par l’interface.

++ Tirer pleinement partie du pattern Command via l’utilisation de CanExecute pour définir la disponibilité des commandes si nécessaire.

?? Considérer la mise en place d’une classe de base ViewModelBase héritée par les ViewModels afin de mutualiser et d’uniformiser les opérations basiques communes : notification d’interface, état IsBusy pour les opérations asynchrones longues, navigation … La plupart des toolkits MVVM propose une classe de ce type.

-- Ne pas « surexploiter » les ViewModels… Le code métier doit se trouver dans une/plusieurs assemblies métier.

-- Ne pas manipuler une référence à la vue. Cela constitue une mauvaise pratique MVVM.

 

Namespace Converters

Les converters sont des classes implémentant IValueConverter permettant de jouer une portion de code intermédiaire dans le processus de DataBinding d’une propriété à l’image de ImageIdToImageSourceConverter qui permet de créer la source de l’image à afficher à partir du chemin déduit d’un Id aplicatif.

 

++ Nommer votre converter avec un nom explicite du type XToYConverter.

-- Ne pas dépendre d’une dépendance externe. Le code d’un converter doit être simple (converter = conversion).

?? Remettez en cause votre Converter lors de sa création/modification... Les opérations du type filtrage, algos spécifiques pour l’affichage doivent être effectuées en amont au niveau ViewModel conformément à MVVM. Lorsque votre Converter se complexifie, c’est souvent que son code n’est pas à sa place.

?? Définir des propriétés à votre classe Converter si nécessaire. Dans notre cas, le converter ImageIdToImageSourceConverter peut définir une propriété DefaultPicture qui serait affichée si la ressource d’image n’est pas trouvée. N instances de ce converter peuvent-être définies avec une valeur de DefaultPicture différente ce qui évite la multiplication de converter quasi identiques (Maintenabilité ++).

?? Utiliser les ConverterParameter que lorsque c’est vraiment nécessaire. Ce mécanisme n’étant pas très flexible…

-- Ne pas définir ConvertBack si votre Converter n’est pas utilisé pour du DataBinding UI->DataContext. Déclenchez une exception du type NotImplementedException dans ce cas.

 

Namespace MarkupExtensions

Les Markup Extensions ont été introduites afin d’étendre les possibilités de XAML (x :Type, x :Static, x :Reference, …). Il existe la possibilité de définir nos propres Custom Markup Extensions à l’image de notre classe ImageEmbeddedResourceExtension qui permet d’afficher une image disponible en Embedded Resource dans un contrôle Image XAML (pratique courante).

++ Utiliser les Custom Markup Extensions pour mutualiser des traitements répétitifs liés aux limitations du XAML.

++ Utiliser les Custom Markup Extensions pour effectuer ce type d’opérations sans avoir à passer par le mécanisme de Binding à la différence des Converters).

?? Il peut être opportun de transformer ou d’exposer un Converter One-Way en Markup Extension pour en améliorer l’accessibilité (inutile de créer une StaticResource avec ce mécanisme).

 

Namespace Platform

Ce namespace fournit des implémentations Xamarin cross-plateformes (et non pas spécifiques à chaque plateforme) utilisées par la couche métier. La classe XamarinSettingsProvider est un bon exemple : elle utilise le mécanisme unifié proposé par Xamarin pour la gestion des paramètres persistés par les applications.

 

++ Utiliser tant que possible les mécanismes cross-plateformes de Xamarin plûtot que de passer par des implémentations par plateforme.

?? Je vous conseille de rechercher des implémentations cross-plateformes fournies pour Xamarin via des librairies tierces plûtot que de partir sur des implémentations spécifiques par plateforme. Il existe de nombreux projets Open-Source sur Github (Xamarin Components,  Xamarin Forms Labs, …).

 

Namespace Core

Le namespace Core de ce projet contient 2 classes centrales : IOCConfigurator permettant d’orchestrer l’inversion de contrôle, ViewModelLocator permettant de résoudre et fournir les instances des ViewModel aux vues.

 

++ Utiliser l’IOC afin de résoudre les implémentations et gérer le cycle de vie de vos dépendances externes.

?? Etudiez les différents frameworks IOC que vous pouvez utiliser sur votre projet selon vos besoins. (Container MVVMLight suffisant ? Unity ? Autofac ? …)

?? Les implémentations fournies par les projets Front Windows/Android/iOS peuvent être fournies à la classe IOCConfigurator via des propriétés/méthodes spécifiques.

?? Il est possible de fournir des ViewModel (pour le Designer) ou des implémentations de Mock selon des conditions spécifiques (DesignMode, paramètre TestMode, directives de Compilation, …)

 

Dossier Theme

Ce dossier contient des fichiers XAML où sont définies les ressouces globales de l’application. Il sont référencés par le point d'entrée App.xaml/App.xaml.cs.

++ Isoler les ressources globales : Couleurs, Brush, Styles, Templates dans des fichiers séparés pour simplifier la cohérence de votre application tout en ouvrant la possibilité de créer des thèmes distincts.

++ Garder une logique cohérente pour la définition des clés de vos ressources. Exemple : TexblockTiTleStyle.

?? S’assurer que le scope des ressources définies dans ce fichier est bien global à l’application. Si ça n’est pas le cas, cette ressource doit être placé à l’emplacement adéquat : fichier commun de ressources pour un sous-ensemble de vues - niveau ressources d’un fichier - niveau conteneur parent - niveau contrôle lui-même.

++ Utiliser le mécanisme d’héritage des Styles afin de mutualiser le plus possible les propriétés définies.

++ Utiliser les styles implicites pour affecter automatiquement des valeurs de propriétés par défaut pour des contrôles XAML.

-- Eviter l’abus de définitions dans les styles implicites telles que la définition de marges customisées qui poseront forcément des problèmes difficilement analysables dans certains cas.

 

Suite

Dans la prochaine partie, je vous détaillerai le contenu des couches spécifiques aux différentes plateformes: BookStore.UWP.dll, BookStore.Droid.dll, BookStore.iOS.dll.

Commentaires