Dot.Blog

C#, XAML, Xamarin, UWP/Android/iOS

Prism pour WinRT – Partie 4 – Gestion des états de l’application

[new:20/07/2013]La partie 1 a posé le décor, la partie 2 a montré la mise en œuvre de base, la partie 3 de cette étude de Prism pour Windows Runtime nous a fait découvrir comment naviguer avec Prism et ce qu’il propose pour la gestion des commandes. La partie 4 aborde la gestion délicate des états de l’application.

Le cycle de vie des applications sous WinRT

Avec Windows et le bureau dit classique les applications ont un cycle de vie simple : elles sont exécutées par l’utilisateur puis à un moment donné terminées, généralement par le même utilisateur.
Avec la nécessité de gérer de nouvelles plates-formes matérielles comme les tablettes et les smartphones le cycle de vie des applications a été bouleversé. Des changements ont été fait en profondeur afin de minimiser l’usage des ressources du système et maximiser la durée de la batterie.

De fait ce qui était rudimentaire sous Linux ou Windows devient sophistiqué pour une application moderne sous iOS, Android, Windows Phone et même WinRT sur PC qui adopte un fonctionnement identique à sa version tablette. Cela peut être vu comme une contrainte inutile pour WinRT PC, et d’un certain point de vue ce n’est pas faux, mais en retour cela permet à une même application, un même code, de fonctionner sur PC et sur Surface… Une différence importante entre ces deux environnements aurait de toute façon déclenché le même type de reproche, mais dans l’autre sens ! (“comment ? MS produit un OS cross-plateforme et il y a de si grandes nuances de programmation ?” – vous voyez ce n’est pas si simple de plaire à tout le monde !).

Comment donner l’impression de fluidité, comment offrir la puissance maximale à l’application en cours tout en laissant à l’utilisateur la possibilité de revenir sur l’application précédente dans l’état où il l’a quittée et sans qu’il ne se doute qu’elle a été arrêtée, voire vidée de la mémoire ?

La réponse se trouve dans la gestion du cycle de vie des applications. Les nuances sont minces entre les OS mobiles et tous adoptent un même mécanisme de “suspension”, d’arrêt temporaire, etc.

Pour WinRT le schéma du cycle de vie est le suivant :

Figure1une vision plus complète des changements d’état

05_cycle_schemaCycle de vie d’une application sous WinRT (illustration empruntée au livre “Développement Windows 8” chez Eyrolles auquel j’ai collaboré)

On voit que trois états dominent : L’état non démarrée (l’application n’est pas lancée), l’état “en cours d’exécution” (l’application est en avant-plan et fonctionne normalement) et l’état suspendue (l’application est dans un état intermédiaire).

On démarre une application, elle passe dans l’état “en cours d’exécution”, le message envoyé est “Activating” (activation). Mais durant le fonctionnement de l’application il n’y a plus la possibilité de l’arrêter ! L’utilisateur ne fait que passer d’une application à l’autre sans se soucier de les arrêter. Sous WinRT il y a une gestuelle (et le raccourci Alt-F4) qui permettent de fermer une application mais cela est anecdotique et ne fait pas partie du cycle normal de vie.

En revanche, le passage d’une application à une autre va placer celle qui est quittée dans ce fameux état intermédiaire qu’est l’état “suspendue” (transition “Suspending”). Dans cet état c’est l’OS qui va décider selon les besoins en RAM et en puissance ce qu’il va advenir de l’application en mémoire. Si elle devient gênante elle sera tout simplement fermée (transition “terminating”). Mais si l’utilisateur revient dessus avant cette opération de vidange de la mémoire elle sera reprise tel quel de la mémoire pour prendre l’avant-plan. Auquel cas la transition sera “resuming” (reprise).

De la cuisine interne allez-vous dire… Hélas non.

Car cette “cuisine” si elle bien gérée par l’OS sans aucun contrôle de l’utilisateur ni même du développeur n’est pas sans conséquences pour ce dernier !

Si votre application passe de l’état actif à l’état suspendu puis est immédiatement reprise (resuming) en effet cela n’a aucune incidence. Mais dans la réalité elle passera souvent par l’état d’arrêt. Que deviennent les données en cours de saisie ? Si l’utilisateur revient sur l’application verra-t-il tout son travail perdu ou bien retrouvera-t-il, comme il s’y attend, l’application dans l’état où il l’a quittée (même en plein milieu d’une saisie non validée) ?

La réponse est très simple : rien. Il n’arrive rien. Par défaut tout est perdu.

C’est grâce aux transitions évoquées, qui sont autant de messages ou d’appels de méthodes, que l’OS prévient votre application. Il lui faudra réagir vite pour sauvegarder tout ce qui doit l’être ! WinRT n’offre que très peu de temps avant de détruire l’application. De même, dans l’autre sens, à la reprise de l’application, en fonction des éléments sauvegardés et du message d’activation (Activating, Resuming) il faudra décider quelle page doit être affichée mais aussi réhydrater tous les objets pour que l’utilisateur se retrouve dans l’exacte situation qu’il a laissée…

Et c’est là que ça se complique un peu.

Pour résumer, le cycle de vie des applications en environnement mobile ajoute un niveau de complexité totalement nouveau qui, bien que signalé par des évènements de l’OS, reste totalement à la charge du développeur…

Prism dans tout ça ?

Ce qu’on attend d’une bibliothèque de ce genre, totalement adaptée à l’OS, c’est bien entendu qu’elle simplifie ce qui est complexe. Ce type d’aide est tellement indispensable qu’on en viendrait à se dire que les OS sont “mal finis” car ils devraient apporter cette couche de simplification d’emblée. Mais comme si la cohérence et la simplicité était un luxe, un bonus, tous les OS mobiles s’arrêtent quelques lignes de code avant cette étape et laisse ainsi le champ libre à des bibliothèques de combler ce qu’ils auraient du proposer dès le départ. Etrange façon de concevoir un logiciel (un OS n’est qu’un logiciel comme un autre).

Quoi qu’il en soit Prism pour WinRT met en place des mécanismes qui aident le développeur à gérer le cycle de vie des applications. C’est l’une des raisons qui dans les parties précédentes nous a obligé à faire descendre les Page d’un type fourni par Prism car en dehors de la mise en page il prend en charge les aspects de navigation et de cycle de vie. Il en va de même pour les ViewModels et même pour l’objet application.

L’application exemple

Pour illustrer ce billet je vais utiliser une application exemple écrite par un membre de l’équipe Prism. Vous pouvez la télécharge directement ici :

Solution HellowPrismWinRT partie4 example.zip

Ajouter des données

L’exemple prolonge le code étudié dans les parties précédentes, une application WinRT avec une page principale et une seconde page. La page Main contient un “menu” qui permet d’accéder à la page de saisie, cette dernière permettant d’ajouter des articles à un charriot comme sur un site marchand.

Dans la partie 3 cette seconde page était restée vide.

Nous allons lui ajouter la saisie d’articles, comme une commande sur un site Web, afin de disposer de données dans deux états différents : “en cours de saisie” et “validées”.

Les données en cours de saisie seront celle qui permettent d’ajouter un article et sa quantité au charriot, les données validées seront représentées en mémoire par la liste d’objets déjà ajoutés.

image

Voici (ci-dessus) la capture de la page secondaire de l’application exemple depuis le simulateur.

Le code source étant fourni (téléchargeable dans le paragraphe précédent), je vous passe les détails de mise en page et d’une partie du code.

Les zones “product” et “quantity” en haut de l’écran permettent de choisir l’article et sa quantité, le bouton “add to order”, ajoute tout cela à la commande en cours, et la liste en dessous récapitule ce qui est déjà dans cette dernière.

La liste des produits disponibles est gérée ici par une simple liste en mémoire qui joue le rôle de la base de données :

image

L’application gère un repository pour la commande en cours :

image

Du C# bien classique sur lequel je ne m’étendrais pas.

Exposer les données pour la Vue

La vue capturée plus haut s’attend bien entendu à trouver certaines propriétés dans le ViewModel comme la listes de produits (Products dans le code XAML), l’ID du produit sélectionné, la quantité choisie et une commande pour le bouton d’ajout. Il faut aussi une liste CurrentOrderItems pour gérer l’affichage des objets faisant déjà partie de la commande.

On retrouve tout cela dans le ViewModel “AddSalepageViewModel” :

image

Une fois encore, rien que du classique, tant côté C# qu’en terme de support du pattern MVVM.

Jusqu’ici ce code pourrait être écrit pour WPF avec Mvvm Light, on ne pourrait pas faire la différence. Beauté de C# et XAML et intelligence de Microsoft ne n’avoir pas totalement tué ces merveilles pour la démagogie totale qu’est la programmation en HTML de WinRT ou le retour de la ringardise pour ce qui est du retour de C++ …

Quelque chose qui cloche…

En l’état du code écrit il y a quelque chose qui ne va pas. Essayez et vous verrez…

Si vous vous contentez de lancer l’application, d’aller en page 2 et de saisir quelques articles vous allez penser que tout va bien.

Maintenant prenons un scénario plus réaliste : lancez l’application, allez en page 2, choisissez un article et une quantité, deux données transitoires, puis arrêtez vous là, et dans VS dans la barre “debug location” (qu’il faut ajouter si elle n’est pas présente) vous trouverez un bouton “Suspend”, cliquez dessus, cela ouvre une liste de choix (Suspend, Resume et Suspend and Shutdown), cliquez sur “Suspend and Shutdown”. Cela va suspendre l’application (comme si l’utilisateur était passé à une autre application) et l’arrêter dans la foulée (comme si durant sa suspensation l’OS avait eu besoin de plus de mémoire).

Relancez l’application (F5 ou le bouton de débogue). Par magie l’application va se lancer sur la bonne page, la page de saisie des commandes, c’est déjà pas mal (merci Prism !) mais l’article sélectionné et la quantité saisie auront disparu…

C’est une violation claire des guidlines qui impliquent que la suspension suivie ou non d’un arrêt puis d’une reprise doit être totalement transparent pour l’utilisateur.

Propriétés auto-sauvegardées

Prism va venir encore à notre secours grâce à un Attribut qui permet de décorer les propriétés à sauvegarder automatiquement “RestorableState” :

image

Les propriétés du VM ainsi marquées seront sauvegardées et réhydratées automatiquement par Prism.

Rejouez le scénario précédent.

L’application reprend exactement là où elle a été arrêtée, les données transitoires (en cours de saisie) sont intactes, les guidelines Modern UI sont enfin respectées, tout est transparent pour l’utilisateur !

Au passage vous noterez que pour le développeur, et grâce à Prism, cela n’a pas été trop compliqué non plus…

Que se passe-t-il si vous avez besoin de sauvegarder des types complexes ou des objets de la couche Modèle ? Il y a une petite étape à ajouter… En interne le FrameNavigationService de Prism se charge de gérer les états et de sauvegarder / réhydrater les ViewModels, mais il le fait en utilisant le DataContractSerializer de WCF qui a besoin de connaître les types qu’il va manipuler. Pour les types de base c’est automatique (entier, chaînes…), mais pour les types custom comme peut l’être la classe Product dans l’application exemple, il est nécessaire d’indiquer au serializer que ce type est “connu”. Cela passe par une ligne de code (une par type complexe à sérialiser) :

image

Il faut ainsi surcharger la méthode OnRegisterKnowTypesForSerialization de la classe MvvmAppBase dont hérite l’objet application avec Prism. Le code est très simple puisqu’il suffit d’enregistrer un à un tous les types qui seront gérés par le mécanisme évoqué plus haut.

Maintenant retournez dans l’application, ajoutez quelques articles à votre charriot et recommencez l’exercice de suspension/arrêt.

Relancez.

Parlesangbleu ! Fichtre ! Diantre !

Hmmm, oui. vous avez raison, vous avez encore quelques problèmes : la liste des objets déjà commandés ne réapparait pas. Il va falloir travailler encore un peu…

ISessionStateService

Le problème c’est qu’il n’y a pas que les ViewModels qui doivent maintenir leur état dans une application. Les états visuels doivent l’être aussi mais Prism s’en charge déjà (position d’une scrollbar par exemple). Mais je veux parler ici d’informations qui ne sont ni dans l’UI ni dans les VM. C’est le premier M de MVVM… Le Modèle (ou les modèles d’ailleurs).

Dans notre application exemple nous avons une liste de produits qui simule une base de données. Mais nous avons aussi un Repository, une couche modèle, qui gère la commande en cours et son contenu. Si la couche SGBD (simulée ou non) n’est pas concernée par nos soucis de réhydratation, la couche Modèle en contre-partie l’est…

Le singleton qui stocke la commande en cours et autorise l’ajout d’items ne sait pas se sauvegarder tout seul. Et n’appartement pas à la logique d’un VM, Prism ne peut rien pour lui directement.

Heureusement Prism ne nous laisse pas tomber. Il propose un service de maintien de l’état d’une session, SessionStateService, accessible via une Interface. Il suffit d’injecter une dépendance à ce service dans tous les objets du Modèle qui doivent être sauvegardés / restaurés.

Ici nous allons modifier le Repository pour injecter dans son constructeur la dépendance au SessionStateService, exactement comme nous l’avons déjà fait pour d’autres services jusqu’ici.

image

La méthode d’ajout d’article à la commande en cours va être elle aussi modifiée pour systématiquement sauvegarder la liste interne dans le SessionState :

image

C’est la dernière ligne de code qui change (qui a été ajoutée). Elle prend l’objet liste interne et le transforme en tableau qu’elle ajoute au SessionState à l’aide d’une clé fixe (voir le code juste avant pour sa définition – le SessionState est un dictionnaire clé/valeur).

Toutefois il manque encore un dernier effort : sauvegarder la liste c’est bien, la récupérer c’est mieux… Dans le constructeur du Repository il est maintenant nécessaire d’aller récupérer le tableau des commandes s’il existe :

image

Si le tableau est présent, on le balaye en appelant la méthode d’ajout d’item. Tout ce passera donc comme si l’utilisateur avait lui-même ajouter les articles (très vite!), ce qui garantit que si des calculs sont effectués (dans une véritable application cela serait certainement le cas : prix total commande par exemple) tout sera fait comme si les articles avaient été re-saisis manuellement.

Fini ?

Allez, encore un dernier effort, si c’est vrai cette fois-ci, juré !

Comme nous utilisons un service par injection de dépendance il va falloir gérer cette injection. Comme nous l’avons vu dans les parties précédentes on peut choisir un mode “manuel” ou bien utiliser un conteneur de type Unity. Dans un cas comme dans l’autre il faudra ajouter au moins une fois du code pour gérer la situation.

Dans la classe application, au même endroit où les autres instances de service sont déjà enregistrées (dans le cas de l’utilisation de Unity, ce que je vous conseille par rapport à la version “manuelle”) nous devons juste enregistrer le SessionStateService :

image

Voilà, c’est vraiment terminé…

Relancez l’application, terminez-là, relancez-là, tout est en ordre. Bravo ! vous avez fait une application WinRT qui respecte les guidances Modern UI sans trop vous fatiguer grâce à Prism Sourire

Conclusion

La gestion du cycle de vie des applications avec les OS mobiles est pénible il faut dire la vérité. Comme je le disais quelque part plus haut on a un peu l’impression que tous les OS se sont arrêtés juste avant l’étape “terminé”. Tous proposent un mécanisme de message ou de méthodes prévenant l’application des changements d’état. C’est bien gentil mais un peu naïf : ces messages servent justement à persister l’état de l’application alors n’aurait-il pas été plus intelligent que d’aller jusqu’au bout de la démarche en proposant un mécanisme unique dans l’OS pour le faire ?

Pourquoi s’arrêter si près du but ? Mystère. Et tous sont à la même enseigne, d’Android à iOS en passant par Windows 8. A croire qu’un seul a bossé et que deux autres ont servilement copié sans se poser de questions…

Je n’aime pas cette sensation d’œuvre inachevée qu’est la gestion du cycle de vie sous tous les OS mobiles, il y a manquement, travail bâclé.

Heureusement, sous WinRT Microsoft a l’intelligence d’avoir le groupe Patterns & Practices qui comble beaucoup des errements de ce type…

Merci à l’équipe Prism de finir le boulot que les gars de l’OS aurait du faire !

Mais Prism ce n’est pas que ça, c’est encore plein de choses, alors …

Stay Tuned !

Nota: cette série d’article sur WinRT est assez longue et chaque article est lui-même d’une taille dépassant un simple billet de blog. Il doit rester des coquilles, mais à force de relire j’ai les yeux qui n’en peuvent plus, j’espère en l’indulgence des lecteurs ! Ceci n’est pas un livre : c’est Dot.Blog, c’est gratuit et c’est plus complet Sourire

blog comments powered by Disqus