Dot.Blog

C#, XAML, WinUI, WPF, Android, MAUI, IoT, IA, ChatGPT, Prompt Engineering

Prism pour Xamarin.Forms – Partie 6

Poursuivons par le 6ème volet de cette étude pratique de Prism pour les Xamarin.Forms…

Articles précédents

Avant d’aborder le sujet du jour, rappelons ce que nous avons vu jusqu'à maintenant :

  • Partie 1, une présentation générale de Prism et divers liens vers d’autres articles
  • Partie 2, Créer un projet, la connexion des Vues et des ViewModels…
  • Partie 3, le système de gestion des commandes
  • Partie 4, La navigation
  • Partie 5, L’Event Aggregator (messagerie MVVM)

Arrivé à ce point nous avons balayé l’essentiel de ce qui fait une librairie MVVM notamment pour environnement mobile ou assimilé (UWP) avec la navigation.

Toutefois Prism ne s’arrête pas là et fournit d’autres services dont il serait bien dommage de se passer.

Le service de Dialogue

Il en va ainsi du service de dialogue.

C’est un service puisqu’il est accessible de partout depuis un point d’accès centralisé, le conteneur IoC. Et sa responsabilité unique et bien cernée consiste à autoriser l’affichage de messages à l’écran.

En quoi un tel service est-il indispensable ?

Les messages (alertes ou autres) ne peuvent émaner que d’un ViewModel car c’est lui qui prend les décisions, plus rarement d’un Model ou d’un autre service. Mais toutes ces structures particulières d’une application n’ont de toute façon aucun droit de manipuler l’UI directement. Or pour afficher un message simple il existe bien une solution au sein des Xamarin.Forms mais son utilisation ne peut se faire que depuis une instance de Page, donc un élément d’UI.

Pire, une App est généralement constituée de plusieurs pages et seule celle qui est affichée détient le pouvoir d’afficher un message. Ce qui oblige à traquer la page active pour que le message puisse être traité par cette dernière uniquement.

De toute façon le ViewModel ne peut en aucun cas utiliser cette facilité qui lui demanderait d’accéder à des méthodes de la page active, donc à l’UI.

Cette série d’articles sur Prism pour Xamarin.Forms n’est pas un comparatif entre les différents “frameworks” MVVM, mais Mvvm Light étant l’un des plus utilisés et l’un de ceux dont j’ai le plus parlé il me semble logique de le mettre parfois en parallèle pour comprendre les apports de Prism. Concernant le service de dialogue là aussi le côté light de Mvvm Light se fait ressentir. Il existe des interfaces, un moyen d’utiliser la messagerie MVVM ainsi que d’autres astuces pour mettre en place un service de dialogue mais out-of-the-box il n’y a rien. A l’opposé, Prism nous offre un service de dialogue prêt à l’emploi. C’est une nuance de taille sur un besoin aussi fréquent, comme ce l’est pour la navigation par exemple.

Deux types de dialogues

Le premier type de dialogue offert par Prism est le plus simple, le message d’alerte. Celui qui n’attend aucune réponse (en dehors de Ok pour le fermer). En toute logique il s’appelle DisplayAlert et accepte plusieurs paramètres : un titre, un contenu, et le nom du bouton qui servira à fermer le dialogue (souvent “Ok”) plus un autre bouton s’il s’agit d’une réponse ok/cancel ou oui/non par exemple.

Prism tient compte de la programmation moderne asynchrone, donc naturellement la méthode s’appelle en réalité DisplayAlertAsync et peut être attendue de façon asynchrone, ce qui est surtout utile lorsque le message attend une réponse ok/cancel.

DisplayAlerAsync affiche un dialogue Modal, ce qui est très important.

La seconde méthode proposée par Prism est DisplayActionSheetAsync. Cette méthode affiche une “action sheet” native de la plateforme. Il s’agit en réalité d’une option iOS qui est simulée dans les autres environnements. Une Action Sheet et feuille d’action n’est qu’un message qui s’agrémente d’une liste de boutons proposant des actions différentes. Cela est particulièrement utile lorsque les choix de réponse au message ne sont pas strictement binaires (oui/non, ok/cancel par ex).

La méthode accepte bien entendu en paramètre un titre. Il existe ensuite plusieurs variantes. Lorsqu’on souhaite s’en servir comme d’un message de validation d’une destruction on utilisera en plus du titre le nom du bouton d’annulation et le nom du bouton de destruction. Là encore cela est hérité de iOS et se trouve simulé pour les autres OS. Le bouton de destruction étant affiché dans une autre couleur par iOS.

Mais même dans ce cas il est possible d’ajouter d’autres boutons. Ce qui est le cas dans la variante plus simple qui ne prend en paramètre qu’un titre et une liste de boutons.

La différence notable entre les deux versions est que celle qui utilise la notion cancel/destruction est mise en page particulièrement pour faire ressortir le bouton de destruction. Sinon tous les boutons sont affichés de la même façon.

Une Action Sheet peut servir de sorte de menu local par exemple lorsqu’il y a une décision à prendre mais plusieurs possibilités de réponse (et que le ViewModel doit attendre de connaître le choix de l’utilisateur avant de continuer).

Comme tout service, le Dialog Service de Prism utilise une interface, IPageDialogueService :

public interface IPageDialogService
    {
        /// <summary>
        /// Presents an alert dialog to the application user with an accept and a cancel button.
        /// </summary>
        /// <para>
        /// The <paramref name="message"/> can be empty.
        /// </para>
        /// <param name="title">Title to display.</param>
        /// <param name="message">Message to display.</param>
        /// <param name="acceptButton">Text for the accept button.</param>
        /// <param name="cancelButton">Text for the cancel button.</param>
        /// <returns><c>true</c> if non-destructive button pressed; otherwise <c>false</c>/></returns>
        Task<bool> DisplayAlertAsync(string title, string message, string acceptButton, string cancelButton);

        /// <summary>
        /// Presents an alert dialog to the application user with a single cancel button.
        /// </summary>
        /// <para>
        /// The <paramref name="message"/> can be empty.
        /// </para>
        /// <param name="title">Title to display.</param>
        /// <param name="message">Message to display.</param>
        /// <param name="cancelButton">Text for the cancel button.</param>
        /// <returns></returns>
        Task DisplayAlertAsync(string title, string message, string cancelButton);

        /// <summary>
        /// Displays a native platform action sheet, allowing the application user to choose from serveral buttons.
        /// </summary>
        /// <param name="title">Title to display in view.</param>
        /// <param name="cancelButton">Text for the cancel button.</param>
        /// <param name="destroyButton">Text for the ok button.</param>
        /// <param name="otherButtons">Text for other buttons.</param>
        /// <returns>Text for the pressed button</returns>
        Task<string> DisplayActionSheetAsync(string title, string cancelButton, string destroyButton, params string[] otherButtons);

        /// <summary>
        /// Displays a native platform action sheet, allowing the application user to choose from serveral buttons.
        /// </summary>
        /// <para>
        /// The text displayed in the action sheet will be the value for <see cref="IActionSheetButton.Text"/> and when pressed
        /// the <see cref="IActionSheetButton.Command"/> will be executed.
        /// </para>
        /// <param name="title">Text to display in action sheet</param>
        /// <param name="buttons">Buttons displayed in action sheet</param>
        /// <returns></returns>
        Task DisplayActionSheetAsync(string title, params IActionSheetButton[] buttons);
    }


image


Selon le terminal l’affichage sera différent puisque rappelons-le les Xamarin.Forms n’utilisent que des éléments d’UI NATIFS. Le look & feel est donc cohérent par rapport à la plateforme. Le but étant bien d’obtenir à la sortie une vraie App iOS, une vraie App Android et une vraie App UWP. Pas une espèce de truc hybridé ne respectant pas les standards de chaque plateforme. Cela est essentiel pour que les utilisateurs qui ont fait le choix d’un OS (uniquement sur la base de l’UI dans 99% des cas) ne se sentent pas trahis par une application au look exotique.

Voici un exemple d’utilisation du service de dialogue :


public class MainPageViewModel : BindableBase
    {
        IPageDialogService _pageDialogService;

        public DelegateCommand DisplayAlertCommand { get; set; }
        public DelegateCommand DisplayActionSheetCommand { get; set; }

        public DelegateCommand DisplayActionSheetUsingActionSheetButtonsCommand { get; set; }

        public MainPageViewModel(IPageDialogService pageDialogService)
        {
            _pageDialogService = pageDialogService;

            DisplayAlertCommand = new DelegateCommand(DisplayAlert);

            DisplayActionSheetCommand = new DelegateCommand(DsiplayActionSheet);

            DisplayActionSheetUsingActionSheetButtonsCommand = new DelegateCommand(DisplayActionSheetUsingActionSheetButtons);
        }

        private async void DisplayAlert()
        {
            var result = await _pageDialogService.DisplayAlertAsync("Alert", "This is an alert from MainPageViewModel.", "Accept", "Cancel");
            Debug.WriteLine(result);
        }

        private async void DsiplayActionSheet()
        {
            var result = await _pageDialogService.DisplayActionSheetAsync("ActionSheet", "Cancel", "Destroy", "Option 1", "Option 2");
            Debug.WriteLine(result);
        }

        private async void DisplayActionSheetUsingActionSheetButtons()
        {
            IActionSheetButton option1Action =
            ActionSheetButton.CreateButton("Option 1", new DelegateCommand(() => { Debug.WriteLine("Option 1"); }));
            IActionSheetButton option2Action = 
            ActionSheetButton.CreateButton("Option 2", new DelegateCommand(() => { Debug.WriteLine("Option 2"); }));
            IActionSheetButton cancelAction = 
            ActionSheetButton.CreateCancelButton("Cancel", new DelegateCommand(() => { Debug.WriteLine("Cancel"); }));
            IActionSheetButton destroyAction = 
            ActionSheetButton.CreateDestroyButton("Destroy", new DelegateCommand(() => { Debug.WriteLine("Destroy"); }));

            await _pageDialogService.DisplayActionSheetAsync("ActionSheet with ActionSheetButtons", option1Action, option2Action, cancelAction, destroyAction);
        }
    }


Dans ce code on voit comment le ViewModel obtient via injection de dépendances le service de dialogue et comment il l’utilise pour afficher des alertes simples ou des Action Sheets (avec la création des boutons et de leur contenu, texte, action..).

Conclusion

Prism se révèle parfaitement taillé pour les besoins d’une application désireuse d’appliquer MVVM et les bonnes pratiques qui gravitent autour du concept. Plus complet que la référence Mvvm Light, plus complet que l’embryon fourni par les Xamarin.Forms, mais plus facile à comprendre et à maîtriser que MvvmCross ou Caliburn, Prism est une valeur sûre, un intermédiaire intéressant sachant faire la balance entre nombre et qualité des services rendus d’une part et simplicité de mise en oeuvre de l’autre.

Je n’ai pas discuté récemment avec Laurent Bugnon qui est le concepteur de Mvvm Light mais je suppose qu’il doit se poser des questions, au moins pour la version Xamarin.Forms. La plateforme elle-même fournie presque tout ce que Mvvm Light fournissait jusqu’à lors, et en ce qui concerne des choses essentielle comme le cycle de vie, la navigation ou les messages Mvvm Light est très en retard par rapport à Prism. Faut-il repenser Mvvm Light pour concurrencer Prism ou abandonner cette course ? J’aimerai une concurrence ouverte car elle est toujours stimulante. Nous verrons ce que sera le choix de Laurent dans les mois à venir…

En tout cas Brian Lagunas avec qui j’avais collaboré pour la création de Prism pour WinRT a su faire preuve d’une grande efficacité et d’un grand pragmatisme dans cette version Xamarin.Forms de Prism. Un type au corps d’athlète mais au cerveau aussi gonflé que ses biceps !

Il reste encore des tas de petites choses à savoir sur Prism comme le service de Logs (principalement pour le debug sous Xamarin.Forms). Mais après 6 articles dédiés à Prism je crois en réalité qu’il est temps de terminer ce survol.

A ce stade soit vous avez accroché et vous découvrirez facilement ces petits bonus, soit vous êtes réfractaires et ce ne sont pas deux ou trois astuces qui feront pencher la balance.

Est-il temps de lâcher Mvvm Light ? Je n’aime pas cette idée car c’est un framework très accessible qui permet à beaucoup de gens d’entrer dans les problématiques MVVM de façon “soft”. Laurent est quelqu’un que j’aime bien aussi. Mais à titre personnel il est clair que sauf revirement de l’histoire dans le futur, je n’utilise plus que Prism pour les réalisation en cours. Je n’ai pas de temps à perdre à réinventer un service de dialogue ou de navigation pour chaque App par exemple (même si on peut reprendre les solutions que je propose dans mon livre sur les Xamarin.Forms où la section MVVM utilise Mvvm Light en montrant comment compléter ce qui manque).

A vous de tester, à vous de choisir, Dot.Blog ne fait que vous informer !

Stay Tuned !

Faites des heureux, PARTAGEZ l'article !