Cette (longue) série d’articles est découpée en plusieurs billets sur ce blog, mais disponible également dans un seul document ici:

http://sdrv.ms/K4BXdN

 

De la Théorie à la Pratique : le cas de l’Application Klout Kikimeter

 

Klout Kikimeter est une application qui utilise les API des services Klout (http://www.klout.com) et Twitter (http://twitter.com) pour mesurer “l’influence” de l’utilisateur sur les réseaux sociaux (le "kiki" pour ceux qui ont un peu d’humour et se demandent pourquoi j’ai appelé l’application comme ça ;)).

 

En termes de fonctionnalités, l’application propose :

  • Un composant principal qui se charge de récupérer et d’afficher le score Klout et les statistiques Twitter
  • Un système de favoris qui permet de mémoriser vos amis afin de suivre leurs scores
  • Un système de paramètres qui permet de mémoriser le nom d’utilisateur principal et d’activer ou pas la vignette dynamique
  • Un background agent permettant de mettre à jour la vignette
  • Un « générateur de vignette » qui sera utilisé par le composant principal et le background agent pour afficher le score de l’utilisateur sur la vignette de l’application dans le start screen.

 

C’est donc une application simple mais qui illustre quand même quelques problématiques classique : modèles métiers, couche d’accès aux données sur des APIs REST/Json, nécessité d’utiliser à la fois des services du matériel et de l’OS.

 

Etape 0 : Repenser l’expérience

 

La première chose qui me vient en tête, c’est la différence de résolution : un écran en mode paysage plus large me permet d’avoir plus d’information en première page, c’est un luxe dont je vais profiter. Je crée donc une page qui regroupe toutes les infos utilisateurs (y compris les topics, influencers, etc) avec les favoris alignés horizontalement en haut. Cela me permettra de me servir de cette liste comme d’une forme de contrôle pivot ou tabulé pour naviguer rapidement entre les fichers de mes amis.

 

Coté comparateur de score, je vais garder l’organisation en colonne, sauf que j’ai beaucoup plus de place. Je vais donc autoriser l’utilisateur à comparer plus de 2 personnes en ajoutant des colonnes au fur et à mesure qu’il sélectionne des utilisateurs.

 

Pour ce qui est des paramètres, de la recherche et du partage du score sur les réseaux sociaux, je choisi de m’intégrer dans l’expérience native de Windows 8 Release Preview. Pour la vignette dynamique, je reste sur le même principe que celle de Windows Phone, avec un design Metro pour Windows 8. Et voilà, j’ai fait le tour !

 

Etape 1: Revue d’architecture de l’application

 

Voici le diagramme d’architecture en fonction des fonctionnalités et du découpage du code métier par rapport à la vue. Le pattern MVVM est à peu près respecté.

 

image

 

Première étape, refactorer les modèles objets, et la couche d’accès aux données, pour les rendre portables. Je crée donc un nouveau projet de type Portable Library qui contiendra :

  • Un dossier avec mes modèles métiers (KloutService / KloutUser)
  • Un dossier avec mes ViewModels : je les découpe suffisament pour pouvoir les composer dans différentes vues.
  • Un dossier « SystemFeatures » qui contiendra des interfaces génériques vers des services qui habituellement sont dépendants de la plateforme : on y retrouvera la gestion des paramètres de l’application, la gestion des favoris (qui dépend de l’isolated storage), une interface vers le dispatcher, et vers le background agent.

 

Du coup, Model et ViewModels peuvent être partagés entre les projets sans problèmes ! Voici le résultat auquel j’arrive :

 

klout kikimeter object model w8 image

 

Théoriquement la couche d’accès aux données est partageable aussi… Si je regarde les dépendances, elle dépend de mon modèle métier (pas de soucis, on vient de voir que c’est un projet de type Portable Library) mais également d’un framework tiers : Json.NET. Heureusement l’auteur publiait, au moment de la rédaction de cet article, une version "Portable Library". Du coup, je peux aussi passer ma couche d’accès aux données dans un projet de type « Portable Library ». C’est idéal, facile à gérer dans le source control, facile à tester indépendamment de la plateforme… bref, quand on peut utiliser la portable library, on gagne sur tous les terrains.

 

Tout le code vraiment purement spécifique à chaque plateforme (que ce soit l’accès au materiel ou au système d’exploitation) est isolé dans un projet à part que je décide de nommer « PlatformImplementation ». Le rôle de ce projet ? Implémenter les interfaces que j’ai défini dans la partie « SystemFeatures » de mon projet de type Portable Library:

klout kikimeter platform implementation w8 image

 

Ici, on peut séparer la gestion de source et réécrire entièrement le code, ceci étant dit, on s’apercevra souvent qu’on peut optimiser un peu : par exemple pour la gestion des favoris : si les APIs de l’isolated storage sont différentes d’une plateforme à l’autre, la sérialisation XML reste la même ! Du coup dans ce cas, je choisi d’utiliser le même fichier dans les 2 projets, et d’utiliser la compilation conditionnelle. Economie de bout de chandelle ? Peut-être, je mutualise à peine une vingtaine de ligne de code. Mais du coup, je suis sûr de ne pas faire d’oubli quand je devrai les faires évoluer. Pas de petits profits, donc ;). On parlera plus spécifiquement de ce projet dans la troisième partie de cet article, pour en voir les détails d’implémentation.

 

Pour les vues, il n’y a malheureusement pas de magie possible. Je les recrée à la main et j’adapte les styles et les templates en premier, ce qui m’aide à construire rapidement quelque chose ayant l’identité visuelle de mon application Windows Phone. Mais pour l’architecture des composants sur la page en revanche, je pars de zéro. Ayant été relativement propre sur le pattern MVVM de ce côté-là, je n’ai quasiment pas de code-behind (quelques chargements associés à des évènements de page) que je peux copier/coller facilement. Je réutilise également les mêmes ViewModels et donc les mêmes Bindings et les mêmes IValueConverters (pour lesquels il faut juste ajuster le prototype). Pour certaines vues notamment pour démarrer sur le comparateur, j’ai quand même pu copier/coller du XAML de Windows Phone vers WinRT sans problème.

 

klout kikimeter projects and views w8 image

 

En ce qui concerne le Background Agent, il ne fonctionne pas du tout de la même manière dans Windows 8 Release Preview et dans Windows Phone. J’ai donc fait 2 projets séparés, avec du code séparé aussi. Cela ne me dérange pas plus que ça car le rôle d’un Background Agent c’est juste d’aller chercher le score (un appel simple à la couche d’accès aux données) et de mettre à jour la tuile (avec le générateur qui est aussi un projet séparé)… je n’ai donc que très peu de code dedans, l’impact en terme de fragmentation par plateforme est mineur. Je vais quand même générer une interface générique qui permet de l’activer et de le désactiver, histoire de pouvoir appeler des APIs unifiées depuis n’importe quelle application de la même manière : c’est utile si vous l’appelez notamment depuis un projet de type Portable Library.

 

Comme vous pouvez le voir je n’utilise que certaines des techniques décrites précédemment pour cette application, et je me permets quelques entorses aux patterns. Chacun devra faire son choix de façon pragmatique et en fonction de ses préférences de code et de gestion de sources ! Voila le résultat final:

 

klout kikimeter projects folded w8 image

 

A cette étape, on a donc notre ancien code, découpé dans des nouveaux projets, et plus rien ne compile ! C’est normal, ça aurait été trop facile sinon. Il reste à « réparer » ce qui ne marche pas et notamment toutes les couches spécifiques à chaque plateforme. C’est l’objectif de l’étape 3.

 

Etape 3 : Fixer ce qui ne marche pas dans le code existant pour coller à la nouvelle architecture

On pourrait en dire énormément sur les équivalences entre les APIs plateformes, certains le font déjà dans des articles que j’ai mentionnés plus haut. Dans cette partie je préfère rester générique, car les APIs de WinRT ne sont pas forcément définitives : il ne s’agit que d’une Release Preview de Windows 8 ! je vais plutôt m’expliquer sur les méthodes et les questions à se poser.

 

Dans la couche d’accès aux données : porter le code Windows Phone vers la portable library

Mon seul problème dans cette étape, c’est l’absence d’une classe souvent utilisée dans Windows Phone mais non présente dans les projets de type Portable Library: la classe WebClient. Pour cela l’approche « wrapper » est très simple à mettre en place :il me suffit d’improviser une classe WebClient contenant uniquement le code qui m’est nécessaire et qui repose sur le couple HttpWebRequest / WebResponse. C’est une feinte pour éviter d’avoir à réécrire du code, ici, l’approche me fait largement gagner du temps (allez… au moins 1 heure ;) mais c’est une petite application, l’impact est plus important sur les couches d’accès aux données plus complexes).

En voici la substance :

namespace KloutKikimeter.ObjectModel.PortabilityHelpers
{
    public class WebClient
    {
        public event DownloadStringCompletedEventHandler DownloadStringCompleted;

        public void DownloadStringAsync(Uri uri)
        {
            HttpWebRequest request = WebRequest.CreateHttp(uri);
            request.BeginGetResponse(state => 
            {
                DownloadStringCompletedEventArgs dscea = new DownloadStringCompletedEventArgs();
                try
                {
                    var req = state.AsyncState as HttpWebRequest;
                    WebResponse response = req.EndGetResponse(state);
                    using (StreamReader sr = new StreamReader(response.GetResponseStream()))
                    {
                        dscea.Result = sr.ReadToEnd();
                    }
                }
                catch (Exception ex)
                {
                    dscea.Error = ex;
                }
                finally
                {
                    if (DownloadStringCompleted != null)
                    {
                        DownloadStringCompleted(this, dscea);
                    }
                }
            }, request);
        }
    }

    public delegate void DownloadStringCompletedEventHandler(object sender, DownloadStringCompletedEventArgs e);
    public class DownloadStringCompletedEventArgs : EventArgs 
    {
        public Exception Error;
        public string Result;
    }

}

Les favoris (un fichier dans l’isolated storage)

Ici on cherche à masquer les APIs spécifiques sous les interfaces génériques définies dans le projet de type Portable Library. Par exemple, la portable library ne dispose pas des mots clefs async/await… hors il faut utiliser ce pattern asynchrone pour accéder à l’Isolated Storage sous Windows 8 Release Preview… ce que je fais en remontant un pattern à base d’events pour garder le coté asynchrone.

 

namespace KloutKikimeter.ObjectModel.SystemFeatures
{
    public interface IFavorites
    {
        event FavoritesLoadedEventHandler FavoritesLoaded;
        void LoadAsync();

        event EventHandler FavoritesSaved;
        void SaveAsync(IEnumerable<KloutUser> favs);

    }

    public delegate void FavoritesLoadedEventHandler(object sender, FavoritesLoadedEventArgs e);
    public class FavoritesLoadedEventArgs : EventArgs
    {
        public IEnumerable<KloutUser> Results;
    }
}

Les paramètres (settings)

 

Pour ce qui est de la gestion des paramètres, WinRT propose un accès unifié aux paramètres depuis les “charms”, c’est à dire la petite barre vertical qui apparait à droite de l’écran quand on glisse de le doigt depuis le bord droite de l’écran, vers le centre dans Windows 8 Release Preview. WinRT propose deux « bibliothèques » pour stocker les paramètres: une bibliothèque locale (les LocalSettings) et une bibliothèque synchronisée avec le cloud (les RoamingSettings). Ces APIs prennent la forme d’un stockage clef/valeur, plus simple à utiliser que l’Isolated Storage, je réécris donc totalement le code pour tirer parti de cette fonctionnalité… Et apprendre qu’il en existe un équivalent sur Windows Phone, chose que j’ignorais, que j’implémenterai plus tard… qui améliorera la portabilité de mon code. Une méconnaissance de l’API Windows Phone aura donc un peu augmenté ma dette technique et un portage sur Windows 8 Release Preview m’aidera à la faire diminuer ! Au final, j’ai donc une interface avec 2 champs (le nom d’utilisateur et l’utilisation ou pas de la vignette dynamique) et 2 méthodes (Load et Save) qui masquent des systèmes totalement différents !

 

namespace KloutKikimeter.ObjectModel.SystemFeatures
{
    public interface ISettings
    {
        string TwitterUserName { get; set; }
        bool IsLiveTileAgentActive { get; set; }

        void Load();
        void Save();
    }
}

Le Dispatcher

 

Autre point assez intéressant, le dispatcher. En effet, comme beaucoup je fais appel au Dispatcher au moment d’appeler l’évènement PropertyChanged du ViewModel. Hors.. ce dispatcher n’existe tout simplement pas dans un projet de type Portable Library, dans lequel j’ai pourtant mes ViewModels ! du coup, je crée une interface IDispatcher avec une méthode Invoke qui prend une Action en paramètre… et j’appelle cette interface dans mes ViewModels. Dans l’implémentation spécifique, je wrappe l’appel soit au Dispatcher Windows 8, soit au Dispatcher Windows Phone.

Voici ci-dessous l’interface, la classe qui l’implémente, et son utilisation :

namespace KloutKikimeter.ObjectModel.SystemFeatures
{
    public interface IDispatcher
    {
        void Invoke(Action a);
    }
}

namespace PlatformImplementation
{
    public class Dispatcher : IDispatcher
    {
#if !WINDOWS_PHONE
        private CoreDispatcher cd;

        public Dispatcher(CoreDispatcher currentDispatcher)
        {
            cd = currentDispatcher;
        }
#endif

        public void Invoke(Action a)
        {
            
#if WINDOWS_PHONE
                System.Windows.Deployment.Current.Dispatcher.BeginInvoke(a);
#else
            await cd.RunAsync(
                Windows.UI.Core.CoreDispatcherPriority.Normal, 
                new DispatchedHandler(a));
#endif
        }
    }
}

namespace Klout_Kikimeter.ViewModels
{
    public class ViewModelBase : INotifyPropertyChanged
    {
        protected IDispatcher Dispatcher;
        public ISettings Settings;

        public ViewModelBase(ISettings settings, IDispatcher dispatcher)
        {
            this.Settings = settings;
            this.Dispatcher = dispatcher;
        }
        
        #region INotifyPropertyChanged implementation
        public event PropertyChangedEventHandler PropertyChanged;
        public void NotifyPropertyChanged(string PropertyName)
        {
            if (PropertyChanged != null)
                Dispatcher.Invoke(() => PropertyChanged(this, new PropertyChangedEventArgs(PropertyName)));
        }

        #endregion
    }
}

Le Générateur de Vignettes Dynamiques

 

Windows 8 Release Preview et Windows Phone proposent une fonctionnalité de “Vignette Dynamique”, mais d’un système à l’autre elles ne sont pas implémentées de la même manière, notamment la logique de « template » qui existe dans Windows 8 Release Preview n’existe pas dans Windows Phone. Encore une fois, il suffit d’implémenter une interface générique (une simple méthode GenerateTile) et d’avoir le spécifique dans un fichier dans le projet PlatformImplementation.

 

Le Background Agent

 

Comme dit précédemment, les Background Agents ne fonctionnent pas du tout de la même manière dans Windows 8 Release Preview et dans Windows Phone. J’ai donc deux projets de deux types différents qui ne partagent pas de code, mais qui implémentent la même interface définie dans mon projet portable library.. interface ultra simple contenant 2 méthodes : ActivateBackgroundAgent() et DeactivateBackgroundAgent().

 

Conclusion

 

La tâche de portage, ou de migration, de l’application Klout Kikimeter aura pris environ 3 jours, sans rien connaitre au développement d’applications Metro/WinRT. Avec le recul, si j’avais eu un niveau de connaissance suffisant en WinRT j’aurai probablement mis moins d’une journée, la tâche la plus longue étant de redéfinir les vues, ainsi que le support des résolutions multiples, ce qui d’une certaine manière peut être vu comme de l’ajout de fonctionnalité, puisque Windows Phone ne supporte qu’une résolution.

 

Voici une évaluation “de tête” de ce que j’estime partager entre les plateformes. Bien-sûr, ces mesures variant grandement d’une application à une autre dépendant à la fois de votre manière de coder et des fonctionnalités que vous utilisez. Ceci étant dit, passer à Windows 8 Release Preview m’a donné plein d’idées pour améliorer l’application, il va donc devenir de plus en plus difficile de fournir ce genre de mesure étant donné qu’à partir de ce tronc commun, chaque version va commencer à bénéficier de spécificités propres à la plateforme

Composant

Code partagé

Views

5% (styles)

ViewModels

90% (exceptions are because of kludges like calling the Dispatcher)

Models and Data Access

100%

Background Agent

0% (different project)

Platform Abstraction (Settings, Favorites, Live Tile)

20%

Overall

80%

Comme vous pourrez le constater, il n’y a donc ni moulinette automatique de code, ni règle, ni méthodes infaillible, pour réaliser cette tâche, il faut comprendre ce qu’on fait, et coller pragmatiquement aux bonnes pratiques.

 

>> Vers la partie 5: Conclusion: Voir plus loin que la portabilité, penser à l’expérience utilisateur sur les différents terminaux. Le “continuous client”.