Dot.Blog

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

Xamarin.Forms–Shell MVVM–La réponse au Quizz de la Vidéo

Dans ma vidéo sortie en complément des quatre articles principaux sur le Shell je terminais sur la problématique MVVM en vous laissant réfléchir à un problème, le fameux Quizz… Et la réponse alors ? Ben c’est maintenant !

La vidéo sur le Shell

Cette vidéo sur le Shell de Xamarin.Forms peut être visionnée ici : https://youtu.be/8Mt6tUzHkDI 

Elle vient en complément des quatre articles parus précédemment sur cette nouveauté de Xamarin.Forms 4, articles dont voici les liens :

Un conseil pour ceux qui débarqueraient ici sans avoir tout suivi : bienvenue ! Mais malgré tout je vous conseille de tout lire dans l’ordre, les 4 papiers listés plus haut, puis la vidéo indiquée et enfin cet article. J’ai éventuellement publié d’autres articles ou vidéos entre temps mais pour suivre le fil de l’histoire d’aujourd’hui il suffit de lire / visionner ce qui est indiqué ici.

Vous pouvez aussi lire la présentation de la vidéo qui ajoute deux ou trois choses : Xamarin.Forms–Le Shell Complément Vidéo !

Rappel du problème

La vidéo présentait un certain nombre de nouveautés de Xamarin.Forms 4 et de Visual Studio 2019 (encore en bêta à ce moment). Le tout en se servant du template de base de VS2019 pour créer une application Xamarin.Forms supportant Shell. Un code facile à retrouver chez vous puisque c’est le template fourni par Microsoft.

Arrivé à la partie MVVM et bien que le template soit conçu en apparence pour supporter ce pattern nous avons mis en évidence un petit problème, quelque chose de pas très propre qui ne correspond pas à l’esprit MVVM mais qui semble rendu obligatoire par la nature même de Shell.

Ce problème est celui de la navigation avec paramètre entre la page liste (principale) et la page détail.

Dans le code du template l’opération est effectuée le plus simplement du monde : en code behind !

C’est à dire que la ListView principale possède un pointeur vers le gestionnaire de changement d’élément courant (OnItemSelected).

Dans le code behind de la page principale ce gestionnaire navigue vers la page détail en passant par l’objet Navigation. La page détail vers laquelle le code navigue est créée à la volée en passant l’item en cours sous la forme d’un paramètre à son constructeur.

Tout cela s’écrit en quelques lignes de code, ça n’a pas l’air si affreux, mais ça l’est !

Le fait que la View instancie elle-même son ViewModel pour alimenter son BindingContext est déjà une concession faite à l’orthodoxie. Normalement il devrait y avoir une indirection qui puisse permettre de changer le ViewModel sans que la Vue ne le sache.

Ca c’est l’idéal. En réalité je considère que cette entorse n’est pas vraiment un problème. Le ViewModel reste testable de façon unitaire et c’est le principal. Quant au fait que la View connaisse la classe exacte de son ViewModel… Elle connait déjà le nom de tous les champs et de toutes les commandes qui lui sont utiles, ne soyons pas hypocrite. Mais dans l’absolu une interface entre la Vue et son ViewModel serait déjà mieux (mais que de code à écrire et à maintenir pour une simple idéologie !). Bref l’entorse à MVVM qui est faite ici n’est pas la plus gênante. Elle ne met rien en péril et pour l’éviter il faut produire beaucoup de code plus complexe donc et augmenter sensiblement la dette technique. Comme je travaille pour des entreprises diminuer le coût de cette dernière est pour moi bien plus important que le respect à la lettre d’un pattern surtout lorsqu’on le pousse très loin. Les bases oui, c’est indiscutable, la perfection idéologique si elle nuit à la stabilité du code ou si elle en augmente de trop le coût, non.

Donc où est le problème ?

Il est dans le fait que la Vue principale ne se contente pas de créer son ViewModel, elle le stocke dans une variable, ça commence à ne pas être bon car cela veut dire qu’elle va s’en resservir et là je n’aime pas du tout… Permissif oui mais pas trop quand même… Et en effet dans l’événement qui est géré dans le code behind (un autre gros problème) elle réutilise la variable ViewModel pour attaquer l’instance et lui passer un paramètre (dans son constructeur).

Là c’est trop.

On peut placer du code dans la partie behind d’une page si ce code ne concerne que l’UI. Par exemple changer une couleur, adapter la taille d’un objet, etc. Et encore on essaye d’éviter tout cela en utilisant du binding, des objets visuels permettant directement en XAML de faire de l’adaptive design etc. Mais dans ce cadre strict écrire du code behind n’est pas interdit par MVVM.

Mais ici ce même code behind s’amuse à la fois à naviguer, donc prendre des décisions, et en plus il manipule directement son ViewModel. Ca fait beaucoup. Trop même.

Il faut donc régler ce problème en gardant en tête le respect des bases de MVVM.

Et c’est là qu’on commence à s’amuser car ces deux lignes de code behind et ce constructeur de la vue Détail avec son paramètre il va en falloir de la ruse pour s’en passer !

Shell bien que récent est comme tous les systèmes de navigation : il se place côté Vue.

La Solution

Enfin, “ma” solution car comme je le disais dans la vidéo ce qui est intéressant c’est de voir comment vous vous avez résolu le problème, confronter nos versions et pourquoi pas trouver une solution encore meilleure !

Mais puisqu’il faut bien commencer je me jette à l’eau et voici la façon de régler le problème tout en observant MVVM.

Voici les étapes qu’il va falloir franchir :

  • Créer une variable Item dans le ViewModel Principal
  • Faire un binding entre l’item courant de la ListView et ce nouveau champ
  • Déclencher la navigation sur le changement de valeur de cet item
  • Utiliser GotoAsync de Shell pour naviguer vers le détail
  • Ce qui demande de déclarer une route dans App.Xaml.cs
  • Compléter l’URL de navigation avec un passage de paramètre
  • Comme ce procédé ne supporte pas autre chose que des string, passer uniquement l’ID de l’item et non l’item lui-même
  • Mettre en place dans la Vue Détail le mécanisme de récupération de paramètres de Shell dans une variable string qui recevra l’ID
  • La propriété créée dans la Vue Détail pour recevoir ce paramètre ne va rien faire d’autre que le transmettre à son ViewModel mais sous la forme d’un Message MVVM bien entendu, pas en attaquant l’instance du VM…
  • Le ViewModel du Détail en recevant la valeur par la messagerie mettra à jour une propriété qui aura été déclarée au préalable
  • Comme nous avons besoin de l’Item et non de son ID, le changement de valeur de la propriété va déclencher dans le ViewModel Détail l’accès au service de données pour obtenir l’Item dont l’ID nous a été passé.
  • Cette fonction étant une Task et un Setter ne pouvant être Async il faudra feinter en utilisant un Task.Run et une expression lambda qui elle mettre à jour la propriété de type Item du ViewModel Détail.
  • Ne pas oublier de faire une copie locale de la valeur dans une variable et d’utiliser cette dernière dans l’appel de la lambda, sinon les closures risques de vous faire perdre le nord !
  • Enfin la propriété de type Item que nous avons ajouté va recevoir la valeur obtenue depuis le service de données. Respectant INPC la propriété va avertir automatiquement les bindings de la Vue de son changement de valeur et …
  • La Vue va se mettre à jour et afficher le détail !

J’oublie certainement un ou deux autres détails, comme le fait que le ViewModel détail se désabonne de la messagerie tout de suite après la réception du message contenant l’ID. Les ViewModel ne sont pas de base “navigation aware”, il faut un framework spécial pour ça (type Prism par exemple). Mais je tiens à rester en MVVM avec ce qu’il y a dans la boîte. Donc on fait le propre tout de suite. De toute façon étant donnée la façon de lier les VM aux Vues, une nouvelle instance est créée à chaque fois, le procédé adopté est ainsi conforme à cette architecture et permet surtout d’éviter les memory leaks !

Conclusion

Transformer deux lignes de code behind en code MVVM propre ça peut être long ! On comprend pourquoi le Template tout simple ne va pas aussi loin car il y a des choix d’architectures à prendre, par exemple dans l’utilisation ou non d’un toolbox MVVM qui possède sa messagerie, qui peut rendre ou pas les VM navigation-aware etc…

Mais de la coupe aux lèvres il y a souvent une distance plus longue qu’on ne l’imagine. Et ici le passage en MVVM de ce petit bout de code pas si moche que ça à première vue n’est pas si simple…

C’est pourquoi je ne vais pas vous gaver de phrases plus longtemps et que vous trouverez ici le code source (prendre le fichier DotVlog022-Shell.7z) de l’exemple corrigé pour jouer avec, le comprendre, et régler d’autres problèmes MVVM qu’il soulève ici ou là et dont je n’ai pas parlé.

Amusez-vous bien et pour ne rien rater ou venir raconter ce que vous vous avez fait :

Stay Tuned !

Faites des heureux, PARTAGEZ l'article !