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
Si votre utilisateur apprécie votre application il y a de fortes chances qu’il cherche à la retrouver sur les différents terminaux qu’il possède. Cet utilisateur s’attendra au minimum à trouver une expérience taillée pour chaque facteur de forme, au mieux, à pouvoir importer et partager ses paramètres, ses favoris, entre ces différents terminaux.
Par exemple, dans l’application Klout qu’on vient de voir, il est tout à fait raisonnable d’imaginer que les favoris et le ranking soit partagés, par exemple à travers un service Cloud, entre l’application téléphone et l’application tablette.
Si la synchronisation des données est la base de l’idée de continuous client, on peut aller plus loin, en imaginant que l’utilisateur pourra « continuer » l’activité qu’il avait sur un terminal directement sur un autre terminal de façon fluide. Par exemple, pour une application autour du cinéma, consulter des fiches de film dans le métro sur son téléphone, et pouvoir retrouver et regarder les bandes annonces des films qu’il a sélectionné dans le métro une fois chez lui, sur sa tablette. Dans toutes ces expériences le rôle du Cloud va devenir prépondérant, donc si vous n’êtes pas familiers avec les concepts d’Azure, par exemple (au dela de l’hébergement ou du stockage, tout ce qui est fédération d’identité, scénarios de synchro online/offline, etc) je vous encourage à y jeter un œil pour comprendre à quel point le cloud pourra enrichir votre application. Il y a d’ailleurs pour Windows Azure un toolkit qui simplifie le développement de backends pour WinRT, Windows Phone mais aussi disponible sur iOS et Android, et vous pouvez le trouver sur codeplex et sur github :
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 :
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.
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 Consumer 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 !
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é.
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 :
Du coup, Model et ViewModels peuvent être partagés entre les projets sans problèmes ! Voici le résultat auquel j’arrive :
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:
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.
En ce qui concerne le Background Agent, il ne fonctionne pas du tout de la même manière dans Windows 8 Consumer 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:
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.
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 Consumer Preview de Windows 8 ! je vais plutôt m’expliquer sur les méthodes et les questions à se poser.
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; } }
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 Consumer 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; } }
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 Consumer 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 Consumer 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(); } }
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 Window w; public Dispatcher(Window currentWindow) { w = currentWindow; } #endif public void Invoke(Action a) { #if WINDOWS_PHONE System.Windows.Deployment.Current.Dispatcher.BeginInvoke(a); #else w.CoreWindow.Dispatcher.InvokeAsync( Windows.UI.Core.CoreDispatcherPriority.Normal, (sender, args) => { a.Invoke(); }, this, null); #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 } }
Windows 8 Consumer 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 Consumer 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.
Comme dit précédemment, les Background Agents ne fonctionnent pas du tout de la même manière dans Windows 8 Consumer 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().
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 Consumer 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”.
Entre le simple usage des mots clefs du langage, et jusqu’à des librairies spécifiques, il y a différentes manières d’architecturer le code et de minimiser la partie spécifique à chaque plateforme. Nous allons commencer avec quelques rappels sur les bases de la programmation orientée objets, puis nous avancerons dans des concepts et des outils plus complexes.
Les Interfaces sont l’outil le plus classique, dans le monde orienté objet, pour ajouter une couche d’abstraction, en spécifiant un “contrat” de classe sans en définir l’implémentation, qui permettrait d’avoir plusieurs classes, par exemple dans des projets différents avec les mêmes champs, propriétés et méthodes. Pour en savoir plus:
L’avantage des interfaces, c’est qu’on peut en implémenter plusieurs dans une classe.
Les classes abstraites peuvent être utilisées quand il faut spécifier une implementation de base qui peut être « écrasée » par des classes qui en héritent (les classes dérivées). Par rapport à une interface, on perd le multi-héritage, mais on peut y gagner une implémetnation par défaut ! Pour en savoir plus :
Les classes partielles permettent de définir une seule classe, dans plusieurs fichiers différents. Elles sont au cœur du framework .NET et particulièrement du couple XAML/C#! Cela permet de découper la défintion d’une classe dans des fichiers séparés qui pourraient, par exemple, avoir une partie dans un projet générique, avec une autre partie dans un projet spécifique à une plateforme (à condition d’être dans le même espace de nommage). Pour en savoir plus sur les classes partielles :
Les méthodes d’extension peuvent aussi être utilisées pour découper le code de méthodes dans différents fichiers : par exemple en étendant une classe générique avec des méthodes spécifiques à chaque plateforme. LINQ est un bon exemple d’utilisation des méthodes d’extension puisque l’ajout d’un espace de nommage et d’une référence LINQ permet d’ajouter des méthodes comme Where() et Select() aux types énumérables. Pour en savoir plus sur les méthodes d’extension:
La portable library permet, en function des frameworks que vous ciblez, de créer des projets “portables” utilisant le sous-ensemble de classes et de méthodes commun à toutes les cibles. La portable library est inclue par défaut dans Visual Studio 11 (sauf pour la version Express) et est disponible en tant qu’extension pour Visual Studio 2010. Les frameworks cibles supportés incluent le framework .NET, WinRT, Silverlight, Silverlight pour Windows Phone, et la Xbox 360.
La portable library, de par sa nature, n’inclue pas d’APIs spécifiques à une plateforme ou un OS, mais contient tous les types de base, les fonctionnalités coeur de .NET, l’accès à Internet et au web, le parsing XML… bref, ce qu’il faut pour créer des modèles objets, et éventuellement des couches d’accès aux données. Malheureusement, très peu de frameworks d’accès aux données tiers ciblent la portable library.
Une chose à garder en tête quand on conçoit l’architecture d’une application : un projet ciblant la portable library ne peut pas référencer de projets qui ciblent des plateformes spécifiques. Cela peut paraitre évident, pour rester indépendant justement, mais il en découle des implications importantes, notamment le fait que la portable library ne peut pas servir de couche intermédiaire entre deux composants spécifiques à une plateforme, ou bien de couche d’abstraction à des services du système d’exploitation par exemple. Pour en savoir plus à propos de la portable library :
Voici également une belle série d’articles par Stéphanie Hertrich qui détaille l’utilisation de cette portable library WinRT, Windows Phone et Silverlight 5 :
La plupart du temps on écrit du code sans penser à la portabilité… en utilisant des classes de haut niveau qui n’existent que sur une plateforme plutôt qu’une classe équivalente qui serait portable. C’est le cas, par exemple, de la classe WebClient, spécifique à Windows Phone, alors qu’il existe la classe HttpWebRequest qui est portable et qui fait la même chose (voire plus, en fait). Dans ces cas-là, 2 solutions : soit réécrire le code, soit ré-implémenter la classe manquante au-dessus de celles qui existent dans le nouveau framework… une sorte de wrapper. L’avantage est que cela permet de ne pas toucher du tout au code existant.
ATTENTION : n’utilisez pas de désassembleurs comme Reflector ou DotPeek, pour « récupérer » une classe dans un framework et la porter dans un autre. C’est une mauvaise pratique !
La “compilation conditionnelle” peut être utilisée quand on veut mélanger, dans le même fichier, du code spécifique à une plateforme, et du code générique. Ensuite il suffit d’ajouter ce fichier à tous les projets, en utilisant le bouton « ajouter un lien » dans la fenêtre d’ajout de nouveaux éléments.
Chacun se fera son idée de la “propreté” de cette méthode, en ce qui me concerne j’apprécie la clarté du découpage entre le code spécifique et le code générique, cela permet de voir facilement comment maximiser la réutilisation de l’une ou de l’autre.
[Attention] Les gestionnaires de sources ne prennent pas en compte les liens entre les fichiers (seuls les “vrais” fichiers sont commité. Il faut donc que les développeurs aient accès aux projets de toutes les plateformes s’ils lient dans leur projets des fichiers d’autres projets.
Toutes les opérations pouvant prendre plus de 50ms dans WinRT sont asynchrones dans Windows 8 Consumer Preview, et font usage des mots clefs async et await. Ce n’est pas le cas dans Windows Phone, mais il est possible de rajouter cette fonctionnalité et ces mots clefs dans le runtime Windows Phone à l’aide de l’Async CTP. Même s’il s’agit d’une CTP, donc non supportée, elle est pour autant suffisamment stable pour être utilisée dans une application. Cela permet de réutiliser les mêmes API et les mêmes patterns de code sur les deux plateformes, et ça peut être particulièrement utile sur tout ce qui concerne l’abstraction du hardware ou du réseau (celles-ci étant quasi systématiquement asynchrones). Vous pouvez trouver plus d’informations sur la CTP Async avec le lien suivant :
Attention, la CTP Async n’est pas installable sur une machine avec le framework .NET 4.5, Visual Studio 11 ou Windows 8 Consumer Preview !
La plupart des développeurs font appel à des frameworks tiers pour simplifier le développement d’applications et compléter les APIs du Framework .NET : par exemple, pour parser du JSON, pour s’interfacer aux APIs des réseaux sociaux, ou pour implémenter simplement des patterns. Dans le cas où vous utilisez des frameworks tiers, assurez-vous que leurs dépendances restent dans les APIs tolérées par WinRT et les applications de type Metro (qui n’ont pas accès à l’intégralité du framework .NET 4.5).
En ce qui concerne la description des interfaces, en XAML, il faut essayer de distinguer au maximum le découpage de la page (la façon d'empiler et de situer les éléments) des éléments purement graphiques : choix des couleurs, des polices de caractères, etc. Plus cette separation sera claire, plus il sera simple de réimporter la charte graphique sur une autre plateforme tout en réarrangeant, indépendament les éléments sur la page pour utiliser l’intégralité de l’écran par exemple, ce qu’il faut absolument faire quand on change de facteur de forme et donc d’ergonomie.
Certains outils d’injection de dépendances comme MEF et Unity auraient pu permettre de découpler certaines parties du code, en faisant encore une fois usage d’interfaces et de bouts de code plateforme spécifiquement séparés et chargés dynamiquement au démarrage. Malheureusement, le Managed Extensibility Framework (MEF) n’est pas supporté dans Windows Phone. Il est donc d’une utilité très limitée dans le cas qui nous occupe…
>> Vers la partie 4: De la théorie à la pratique: le cas de l’application Klout Kikimeter
Comme dit dans la partie précédente : avant de plonger dans le code, travaillez votre expérience utilisateur : que ce soit simplement avec un papier et un crayon, ou bien avec Sketchflow, ou PowerPoint, ou n’importe quel autre outil de mockup. Ce travail vous fournira une ligne directrice pendant tout le reste du travail de portage.
[Dette technique] http://en.wikipedia.org/wiki/Technical_debt
L’idée de dette technique est la suivante : le plus vous « cassez » votre code en y appliquant des corrections rapides et pas forcément très propres, le plus vous aurez de problèmes à résoudre pour faire évoluer votre code. L’ensemble de ces problèmes représente votre dette technique.
La première étape quand vous portez votre application sur une nouvelle plateforme, c’est de revoir votre code. Cette revue de code est souvent prise un peu trop à la légère au profit de résultats rapide, et aussi parce qu’en tant que développeur on a tendance à croire qu’on connait son code par cœur. En fait, durant tout le développement et particulièrement durant le débuggage et la phase de tests pré-certification, nous cassons tous les patterns pour résoudre des « petits bugs », tout en accroissant notre dette technique. Tous ces petits fix ne sont pas forcément très lisibles, ni frais dans notre mémoire. Enfin, même si vous avez déjà revu votre code dans le passé, le revoir une nouvelle fois avec l’idée du portage en tête vous en donnera surement une lecture différente.
Le but de cette revue de code est donc de rafraichir votre mémoire sur les différents composants de votre application, les couches de votre architecture, et à quel point ils sont dépendants les uns des autres. A la fin de cette revue de code, vous aurez probablement en tête un plan de refactoring du code pour préparer le portage... Une étape que vous auriez surement négligée si vous n’aviez pas fait cette revue !
A ce point du processus, il est facile de céder à la tentation de copier vos fichiers sources dans un nouveau projet d’application Metro, et de commenter tout ce qui ne compile pas, pour avoir un début de prototype en 10 minutes. C’est une approche pragmatique mais qui risque d’être pénalisante pour votre utilisateur, car elle vous pousse à conserver une expérience existante parfois inadaptée, et vous risquez d’augmenter votre dette technique alors que le but de la revue de code précédente était justement de la diminuer !
Pour chaque projet de votre solution, vous devez lister les références que vous utilisez et à quel point elles sont portables. Si vous utilisez des frameworks tiers, il faut savoir s’il en existe des versions portables (soit à travers la portable library, ou ciblant spécifiquement chaque plateforme). Le résultat de cette investigation sera surement que cela existe, étant donné que c’est le but même de l’existence de ces frameworks… Mais parfois vous constaterez que les fonctionnalités ne sont pas les mêmes d’une version à une autre, ou bien qu’elles ne sont tout simplement pas au même niveau de maturité ou de stabilité. A vous de voir dans ce cas, si vous souhaitez vous attaquer plutôt au portage de l’existent ou tenter de remplacer par un framework concurrent. En tout état de cause, c’est plus facile si le code source existe, et dans ce cas-là, pourquoi ne pas contribuer à la communauté ?
En ce qui me concerne, j’utilise surtout Json.Net et parfois MVVM Light, qui sont tous deux très portables.
Pour chacun des fichiers sources, essayez de nettoyer vos “using” par exemple en utilisant la fonctionnalité de Visual Studio qui permet de les réorganiser. Avoir la liste la plus courte possible de « using » permet de comprendre plus facilement les dépendances de vos fichiers et de vos références.
Pour chaque appel à une fonctionnalité ou une API spécifique de la plateforme, voyez si l’équivalent existe sur l’autre plateforme. Il y a quelques billets dans la communauté qui relatent l’expérience des développeurs sur le sujet :
Le dernier lien pointe vers une série de billets de mon collègue Jared Bienz qui promet d’être particulièrement bien détaillée.
Si vous disposez d’une version de Visual Studio qui embarque les outils d’architecture (Premium ou Ultimate), c’est peut-être aussi l’occasion de s’en servir : ils sont capables de générer des graphes qui permettent de voir tout de suite à quel points vous avez cassé les patterns que vous avez utilisé, et les dépendances entre vos composants.
Voici un exemple de ce que j’obtiens avec une application cross-platform en cours d’écriture :
On voit à la fois dans l’application elle-même (FoursquareTracker) le pattern MVVM et la séparation des namespaces (qui correspond à peu près aux assemblies) et les dépendances à des namespaces externes.
Cette étape vous permettra d’avoir une idée à des dépendances de vos fichiers entre eux, et vous mènera naturellement à l’étape 2, qui consiste à ré-architecturer le code en modules portables individuellement. Une fois que ces modules seront découpés, il ne restera plus qu’à choisir la bonne stratégie de portage pour chacun.
Pour le reste de l’article nous allons considérer qu’un composant (ou module) est un bout de code faiblement lié au reste de l’application. Si vous n’avez pas encore réussi à isoler vos composants (ce que j’appelle du code « spaghetti »), il faut revenir à l’étape de revue de code et de refactoring. En tout état de cause, démarrer un portage sur du code spaghetti c’est s’assurer à terme de perdre du temps et de passer un assez mauvais moment à fabriquer quelque chose de bancal. C’est donc le dernier moment pour faire votre nettoyage de printemps, et ça commence avec le choix de bons design patterns…
Un design pattern est une solution prouvée à un problème récurrent. C’est le résultat de travail de développeurs qui ont essuyés les plâtres avant vous. La plupart des designs patterns ont un objectif principal qui est la « séparation des problèmes » (separation of concerns). En d’autres mots, faire en sorte que le code soit découpé en unités fonctionnelles faiblement couplées pouvant évoluer indépendamment les unes des autres. Utiliser un design pattern permet souvent d’améliorer la testabilité du code, sa lisibilité, et même, sa portabilité ! Il faut toutefois faire attention à ne pas surutiliser les patterns, un défaut classique de développeur chevronné qui oublie que si un jour ce code est repris par quelqu’un de moins sénior, il sera surement moins lisible car il sera plus difficile de comprendre le rôle de chacun des composants… amenant donc l’effet inverse que celui escompté : des problèmes de maintenabilité, de performances, et d’évolutivité (difficile pour un développeur novice de faire évoluer correctement un code qu’il ne comprend pas !)…
Voici quelques patterns que vous devriez avoir en tête en pensant « portabilité »
En plus des design patterns, essayez de nettoyer votre code au maximum en le factorisant (ce que nous avons tendance à faire naturellement en C# mais pas forcément en XAML : hors les DataTemplates et les Styles sont là pour ça, et s’appliquer à soigner son XAML facilitera le portage des vues, qui est sans doute la plus grosse partie du travail à réaliser, comme on le verra par la suite).
Il y a différents moyens de séparer les composants dans votre code. La manière que je qualifie « d’horizontale » consiste à séparer le code sous forme de couches métiers, par exemple entre les modèles objets et les objets contenant les données à afficher. Ces composants s’étagent en général de bas en haut dans un diagramme d’architecture. La manière « verticale » de séparer les composants consiste à essayer d’isoler plutôt les fonctionnalités de l’application : par exemple, la fonctionnalité « gestion des favoris » ou la fonctionnalité « comparaison des scores ».
Une fois que vous avez séparé vos composants horizontalement et verticalement vous obtenez une sorte de matrice de composants. Cette matrice de composant va vous permettre d’examiner chacun des composants individuellement pour choisir la meilleure technique de portage.
Dans cette étape, pas besoin de gants : on casse avant de reconstruire. Il faut prendre les morceaux de codes (qu’ils soient dans des fichiers séparés ou pas, et peu importe l’architecture actuelle du projet) et les réordonner, en fonction des stratégies de portage que vous adopterez pour chacun des composants… pas la peine, donc d’en écrire plus pour le moment. Voyons ces techniques !
>> Vers la partie 3: Techniques d’adaptation et de partage de code
En tant que développeur vous avez probablement une vue orientée “fonctionnalités” de votre application. C’est une vue cohérente qu’il est important d’avoir mais pour que l’opération de portage soit vraiment bénéfique à l’utilisateur, il faut se mettre à sa place, et penser que l’usage de votre application sera probablement différent d’une plateforme à une autre.
Un aspect particulièrement important est le facteur de forme : il s’agit de profiter des 10 pouces en mode paysage que propose un écran de tablette par rapport aux 4 pouces en mode portrait d’un écran de téléphone par exemple ! Une ergonomie pertinente sur un type d’écran ne le sera pas forcément sur un autre, particulièrement si on considère la manière d’interagir avec ce terminal : avec un ou plusieurs doigts, ou avec une souris dans le cadre d’un PC.
Toujours sur le même sujet, il faut savoir que les applications « Metro » tournant sur la Windows 8 Consumer Preview peuvent fonctionner en plein écran, en mode « snapped » ou en mode « filled » c’est-à-dire en partageant l’écran avec une autre application. Pour chacun de ces modes les proportions de l’écran sont différentes, et l’usage que l’utilisateur en fera aussi (puisqu’il fera 2 choses à la foi) !
Un autre exemple de point important à garder en tête est la présence (ou l’absence) de certains capteurs comme le GPS, ou bien les capteurs d’accélération ou de position.
Coté logiciel, il y a aussi des différences significatives, pas forcément dans les fonctionnalités, mais plutôt dans la manière de les utiliser. Par exemple les paramètres, la recherche ou le partage unifiés entre les applications dans Windows 8 Consumer Preview ne fonctionnent pas de la même manière que Windows Phone.
En conséquence de quoi, avant d’aborder la partie technique du portage de votre application, je vous encourage à repenser l’usage en fonction du facteur de forme… Il y a de nombreux articles détaillant l’expérience utilisateurs de Windows 8 Consumer Preview ou de Windows Phone qui pourront vous guider dans votre réflexion:
· Pour Windows Phone : User Experience Design Guidelines for Windows Phone
· Pour Windows 8 Consumer Preview : Windows Dev Center: Designing UX for Apps
>> Vers la partie 2: Le processus de migration/portage du code
Il arrive que l'’on doive échanger des données sensibles, par exemple à travers du réseau, ou entre des couches applicatives. Pour cela on a accès à toute une couche de sécurité qui est très simple à utiliser et qui inclue les algos les plus classiques, qu’il s’agisse de crypto (AES, RSA etc) ou du hashage (HMAC, SHA1, SHA256…). Cette couche permet de crypter un contenu à partir d’une clef secrète ou d’un couple clef publique / clef privée.
Mais parfois on veut stocker ces données, et autant on peut utiliser ces algos pour stocker les crypter, on ne peut pas les utiliser pour stocker la clef de cryptage elle-même! Dans ce cas, on a la Data Protection API (DPAPI) qui utilise une clef secrète contenue dans le téléphone, impossible à récupérer et qui permettra de crypter n’importe quel type de contenu. Cette DPAPI est accessible depuis la classe statique System.Security.Cryptography.ProtectedData. Celle ci contient 2 méthodes, Protect et UnProtect, qui agissent tout simplement sur des tableaux de bytes:
Un exemple de cryptage:
byte[] dataBytes = System.Text.Encoding.UTF8.GetBytes(data); byte[] encryptedData = ProtectedData.Protect(dataBytes, null);
Et un décryptage, aussi simple!
byte[] decryptedData = ProtectedData.Unprotect(encryptedData, null); result = new string(System.Text.Encoding.UTF8.GetChars(decryptedData));
En tout état de cause, si vous n’avez qu’à stocker des données cryptées et pas forcément à les partager, vous pouvez ne vous servir que de cette classe. Ca simplifie grandement l’écriture de certaines applications comme par exemple les coffres de mots de passe… mais n’oubliez pas de gérer, lors de vos cryptages/décryptages, les tombstoning correctement… et n’écrivez jamais nulle-part un secret en clair!
Vendredi 9 Mars se tenait le deuxième livemeeting de l’accélérateur, dont le sujet était la monétisation des applications.
La première partie couvre les différents business models avec leurs avantages et inconvénients, et les clefs pour réussir le lancement d’une application. Cette partie a été présentée par Julien Amouroux de Nokia.
Voici le lien vers sa présentation
La deuxième partie couvre l’utilisation du SDK Microsoft Advertising, et une petite démonstration d’intégration de celui-ci dans une application, tout ceci par Aurélien Lemoine, de ClevLab (twitter).
Aurélien a posté sa présentation sur le skydrive de ClevLab.
Enfin, sachez que vous pouvez également revoir le webcast !
A vous de nous dire maintenant ce que vous aimeriez vois dans le prochain livemeeting!
Une CTP de la version 7.1.1 du SDK Windows Phone “Tango” a été annoncée au Mobile World Congress, et sur le blog de l’équipe Windows Phone. Au menu, plutôt des bonnes nouvelles: la mise à jour “Tango” ouvre la porte à des terminaux plus low-cost, particulièrement utiles dans les pays émergents, ajoute le support d’un certain nombre de langues et de pays pour le marketplace, et cela se traduit par un nombre d’utilisateurs potentiels de vos apps qui grossit d’environ 60% d’un seul coup…
Parmi les concessions faites sur le châssis hardware pour diminuer les coûts des téléphones, il y a la possibilité de faire des terminaux avec seulement 256Mo de RAM. Ce choix a pour conséquence qu’une application qui consommerait plus de 90Mo de mémoire serait coupée automatiquement… Cela concerne moins de 5% des applications du Marketplace et nous travaillons avec ces développeurs pour les aider à revenir sous cette barre fatidique.
Pour tester si c’est votre cas, la CTP de la version 7.1.1 du SDK Windows Phone amène une version “bridée” à 256Mo de l’émulateur. il n’y a en gros que ça d’intéressant dans cette version, qui n’est de toutes façons pas encore mure pour publier des applications sur Marketplace (pas de licence dite “go live”). Pas besoin de se jeter dessus, donc, surtout pour votre machine de production, mais si vous êtes dans les 5% à consommer plus de 90Mo, il peut être bon de le tester rapidement.
Si vous savez que vous avez systématiquement besoin de plus de 90Mo, vous pouvez l’indiquer dans votre manifest, et alors l’application n’apparaitra plus aux utilisateurs des téléphones low-cost, évitant l’expérience “j’installe mais ça plante”.
Il suffit de rajouter, dans le fichier WMAppManifest.xml, sous les capabilities les 3 lignes suivantes:
<Requirements> <Requirement Name="ID_REQ_MEMORY_90" /> </Requirements>
Si on résume, soit vous utilisez moins de 90Mo, et tout va bien, soit vous utilisez plus de 90Mo de RAM, et là 2 options:
Votre application n’est pas sensée dépendre d’un background agent pour fonctionner correctement: dès le début, vous deviez prendre en compte le fait que vos utilisateurs peuvent les désactiver manuellement dans le panneau de configuration. Sur les terminaux à 256Mo de RAM, ils ne tourneront pas, de toutes façons. Une raison de plus d’y penser, donc, en créant l’expérience de votre application…
Réponse: En utilisant les DeviceExtendedProperties:
try { long result = (long)DeviceExtendedProperties.GetValue("ApplicationWorkingSetLimit"); } catch (ArgumentOutOfRangeException) { // The device has not received the OS update, which means the device is a 512-MB device. }
La racine de tout ce qui concerne les performances des applications Windows Phone est sur cette page:
Si vous êtes dans le cas d’un jeu en XNA (particulièrement sensible à l’usage de la mémoire) vous pouvez vous tourner vers:
Une session qui a été jouée au Techdays 2012 sur le sujet sera par ailleurs bientôt en ligne…
Pour conclure, je dirais bien que pragmatiquement à partir du moment où on code propre, et où on avait regardé dès le début les contraintes de certification de Marketplace (qui incluent, depuis la première version, la limite de 90Mo), il n’y aura pas de problème. Et pour ceux qui auraient une situation particulière, lié à un usage spécifique ou à une erreur bénigne dans le code au lancement des background agents, la résolution est facile! A vous de jouer!
Source: Developping for 256-MB devices sur MSDN
Le ViewPort se défini dans le code HTML. Il indique la largeur “virtuelle” de la page web. Sur un terminal mobile, on a envie d’ajuster le ViewPort en fonction de la largeur de l’écran, et d’une plateforme à une autre, ça peut varier (ça sera la même pour tous les Windows Phone). Le ViewPort permet de définir plusieurs propriétés:
Certaines plateformes définissent des paramètres comme minimum-scale, maximum-scale, ou initial-scale… Ils ne sont pas supportées par IE9 dans Windows Phone.
Le truc classique en manipulant le viewport pour supprimer l’effet de zoom, c’est de passer le paramètre user-scalable à “no” et le paramètre “width” à “device-width”:
<meta name="viewport" content="width=device-width, user-scalable=no"/>
Mais cela ne suffit pas! En effet, si l’utilisateur essaie de zoomer, la page zoomera quand même… puis reviendra en place automatiquement, empêchant d’afficher un autre niveau de zoom que celui prescrit initialement, mais indiquant à l’utilisateur que son geste a bien été pris en compte (bonne pratique d’ergonomie en général, mais pas top quand on veut cacher qu’on utilise une webview… parce qu’on sait tous que c’est pourri!).
Pour supprimer cet effet, il faut supprimer carrément les évènements de manipulation au niveau du contrôle WebBrowser. cela nécessite un peu de code qui m’a gentiment été soufflé par nos amis d’Ucaya, et plus particulièrement Romuald, dont l’origine est ce post de Colin Eberhardt:
private void WebBrowser_Loaded(object sender, System.Windows.RoutedEventArgs e) { var border = ((Microsoft.Phone.Controls.WebBrowser)sender).Descendants<Border>().Last() as Border; border.ManipulationDelta += Border_ManipulationDelta; border.ManipulationCompleted += Border_ManipulationCompleted; } private void Border_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e) { // suppress zoom if (e.FinalVelocities.ExpansionVelocity.X != 0.0 || e.FinalVelocities.ExpansionVelocity.Y != 0.0) e.Handled = true; } private void Border_ManipulationDelta(object sender, ManipulationDeltaEventArgs e) { // suppress zoom if (e.DeltaManipulation.Scale.X != 0.0 || e.DeltaManipulation.Scale.Y != 0.0) e.Handled = true; //// optionally suppress scrolling //if (ScrollDisabled) //{ // if (e.DeltaManipulation.Translation.X != 0.0 || // e.DeltaManipulation.Translation.Y != 0.0) // e.Handled = true; //} }
Evidemment, il faut associer la méthode WebBrowser_Loaded à l’évènement Loaded du contrôle WebBrowser…
Petite précision à propos de ce bout de code: il utilise la très excellente extension LinqToVisualTree qu’il faudra donc ajouter également au projet…
Et voila, plus d’effet de zoom. Ceci étant dit, ça ne doit pas vous encourager à utiliser les WebBrowser pour autre chose qu’afficher des pages HTML… et surtout, quelqu’en soit votre utilisation, ne les utilisez jamais, AU GRAND JAMAIS, dans un Panorama ou un Pivot, ça casse l’expérience de navigation horizontale. Il y a une place spéciale en enfer pour les gens qui font ça. Vous voilà prévenu!
Les contrôles de type “WebBrowser”, (ou WebView) sont rarement bien utilisés…
Un WebBrowser, c’est fait pour afficher du contenu web (du HTML) parce qu’on ne veut/peut pas le parser (par exemple, une page d’authentification OAuth).
Malheureusement 90% du temps c’est ça qu’on voit. Il ne faut pas faire. Une expérience native c’est justement pour avoir quelque chose de spécifique à chaque plateforme, en terme d’ergonomie, de look’n’feel. C’est ce que les utilisateurs attendent! Malheureusement le développement d’application n’est pas toujours piloté par le développeur soucieux de la beauté du code, ou le créa soucieux de la beauté des visuels et des animations. Des fois, le responsable du projet qui n’a pas assez de sous, ou de temps, et qui doit faire avec ce qu’il a… et on se retrouve avec des webviews mal utilisées.
Alors quand on utilise un contrôle WebBrowser, pour de bonnes ou de mauvaises raisons, il faut en contrôler le comportement. Sinon on se retrouve sur wtfmobileweb.com.
Encore merci à @RomuxX pour l’info, et pour d’autres lectures intéressantes sur le sujet, je vous encourage à aller voir la page sur le développement web pour Windows Phone 7.5, sur MSDN.
Comme pour tous les e-camps, vous pouvez retrouver le webcast directement sur la page de l’évènement (en cliquant sur “Afficher en ligne”). Quant aux slides, je les ai publiées sur ce skydrive, les voici :
D’une manière générale sachez que chaque e-camp peut être retrouvé directement sur sa page d’inscription, une fois qu’il a été enregistré (le jour J) il peut être revu.
Suite à cette session mémorable dont le but était d’exposer les “petits projets” de quelques geeks bien fondus pour leur maison, voici le contenu de la session… le webcast, les slides, le code!
Pour retrouver la session il faut cliquer sur le 4ème chapitre dans la barre de défilement
Fabien (@ftanquerel) de D-Cube a commencé par nous faire une petite démonstration d’interfaçage avec du hardware à base de Netduino Plus : il nous explique le matériel utilisé, et nous fournit slides et codes sources sur le blog D-Cube
Salah (@proteus91300) nous montre quel matériel utiliser pour automatiser sa maison en X10 et le service WCF qu’il a développé pour interfacer sa maison avec son téléphone, son Nao, Kinect… bref un peu tout. Bonus à la fin, il nous parle de proteus, sa salle serveur chez lui, avec robot lego monté sur rails pour allumer/éteindre les machines, et serrure biométrique… et vous pouvez retrouver à la fois ses slides et son code source sur cet article de son blog.
Je montre dans cette partie comment on utilise un Netduino Plus et OpenSenSe pour mettre son climatiseur sur internet et pouvoir avoir des relevés de température réguliers, une automatisation de son démarrage en fonction de la chaleur dans la pièce, et un contrôle à distance. Les slides et le code sont sur ce skydrive là!
David (@deltakosh) et Stan (@squastana) ont fait des recherches avancées, field-testés et wife-proofées pour se débarasser de leurs dvds acquis légalement en les stockant sans pertes de qualité ou de données dans des gros NAS, et conçu à base de matériel (peu corporate on en conviendra) un mediacenter ultime et pas cher. Leur présentation est disponible dans ce même skydrive
Pour terminer, je dirai que le futur de cette session est à écrire! on peut garder ce format avec d’autres projets, on peut imaginer des sessions “geek in the office”, “geek in the car”… j’ai même eu une proposition “geek in the kitchen” Alors lancez-vous, bidouillez, codez, racontez nous vos projets, faites vous connaitre, bref, partageons!
Voici un petit dossier avec les slides et la démo de la session EMB202, sur la conception d’applications robotique avec Kinect, et son intégration dans Robotics Developer Studio 4
Les slides:
La totale avec la démo disponible dans ce dossier skydrive
Pour ceux qui s’intéressent plus particulièrement à la plateforme robotique que j’utilise dans cette session, il s’agit d’Eddie de chez Parallax
Prochain rendez-vous pour la robotique chez Microsoft, Innrobo, les 14,15 et 16 Mars, à Lyon!
Ce vendredi, à la pause déjeuner, 13h30 – 14h, retrouvez moi pour un petit livemeeting de 20 minutes + 10 minutes de questions/réponses sur le thème des animations et des transitions. Ces dernières sont un point clef pour rendre votre application fluide aux yeux de l’utilisateur, le guider dans l’expérience, et c’est souvent un des détails qui font la différence entre une bonne et une excellente application.
Voici le lien pour s’inscrire et suivre l’évènement!
Pensez à être là 5 minutes en avance pour vérifier que la connectivité est bonne, il s’agit d’un livemeeting! et si vous ne pouvez être là, sachez que ce livemeeting sera webcasté, et accessible avec ce même lien!
Petite astuce qui gagne à être partagée évoquée lors d’une discussion avec David Catuhe ce matin : la possibilité de récupérer les stacktraces des crashes de votre application après l’avoir publiée (c’est à dire les crashes que subissent vos utilisateurs)! petit guide pour trouver cette fonctionnalité (un peu planquée, et en béta) de Marketplace.
Quand vous êtes loggés, accédez à votre dashboard Windows Phone 7
Dans le dashboard, il y a la case “App Highlights” dans lequel vous avez les downloads récents, ainsi que les crashs:
Et là, si vous cliquez sur le nom de l’application, ça vous emmène sur les détails (reviews, etc)…. mais si vous cliquez sur le nombre de crash vous arrivez sur un joli graph de reporting:
observez que vous avez un bouton en haut à droite, qui permet de télécharger un fichier excel avec les dernières stacktraces (c’est une fonctionnalité en beta)
Voici un exemple de crash report détaillé, avec la stack trace et la fonction qui pose problème
(bien entendu le bug qui cause ce crash a été créé uniquement dans le but d’écrire cet article, et pas du tout à cause d’une erreur de débutant dans le code de votre serviteur… )
Si vous voulez plus de détails, il faudra vous tourner vers une solution d’instrumentation plus précise, comme par exemple BugSense (petit article dessus de l’ami Fabien).
Voici les slides du Livemeeting d'u 20/12/2011, organisé dans le cadre de l’Accélérateur Windows Phone 7. Le webcast devrait être disponible rapidement, j’updaterai ce post quand ça sera le cas!
Le but de ce livemeeting était de fournir des trucs pratiques tout au long du cycle de développement d’une application, que ce soit des outils de mockup, des petits toolkits et des librairies à connaitre… le slide deck n’est pas bien long car il y a eu beaucoup de démonstrations sur Visual Studio 2010 et Expression Blend, mais vous retrouverez dedans tous les liens utiles.
A bientôt pour d’autres livemeeting dans le cadre de l’accélérateur!
Il y aura aux Techdays cette année une douzaine de sessions sur Windows Phone, la plus grande majorité concernant le développement d’applications. Le parcours va s’intéresser aux arguments qui font les “bonnes” applications… celles qui plaisent à leurs utilisateurs et qui se comportent particulièrement bien.
On va retrouver un mix de sujets “classiques”, comme l’analyse de performances, ou l’architecture et la communication d’une application avec un backend, et des sujets plus exotiques, sur des cas d’utilisations qui ont été ouverts avec Mango, comme la réalité augmentée, l’utilisation d’une base de donnée locale… Le but étant de pouvoir proposer aux nouveaux développeurs de connaitre dès le démarrage les bonnes pratiques de développement, et aux développeurs chevronnés de parfaire leur application ou leur art en partageant avec leurs pairs.
Le parcours n’oublie pas les clients “entreprise” dont les besoins en applications métiers vont croissant et qui, avec Windows Phone 7.5, ont maintenant la possibilité de créer et déployer des applications pour leurs collaborateurs.
Voici la liste des sessions:
Comme promis (bien qu’avec un peu de retard) voici les slides du Windows Phone 7.5 Design Day du 8 Novembre, au campus Microsoft. Une journée pendant laquelle Arturo Toledo (@arturot) et Corrina Black (@corrinab) sont venus présenter les méthodes et les outils de conception d’une expérience Windows Phone 7.5. Durant cette journée, nous avons accueilli plus de monde qui prévu, ce qui nous aura conduit à aménager une deuxième salle en parallèle pour la matinée, durant laquelle j’aurai présenté ce slide deck là, aussi sur Metro.
Merci beaucoup à tous d’être venus aussi nombreux, et sachez que nous allons annoncer très bientôt, pour début janvier, une autre journée sur le thème de l’ergonomie et du design!
Vous pouvez également retrouver un post d’Arturo sur son blog qui parle de son expérience pendant sa tournée d’Europe!
Les techdays commencent à pointer le bout de leur nez… dans l’équipe on y passe déjà beaucoup de temps. Une des tâches principales est l’organisation des parcours et des sessions dans chaque parcours (track).
Un parcours sur un sujet se doit d’être cohérent, et d’avoir un bon équilibre entre les sessions: sujets, niveau technique… L’an dernier par exemple, le parcours Windows Phone 7 concrétisait un retour d’expérience complet sur les différentes phases du développement d’une applications: bonnes pratiques pour démarrer, application du design Metro, collaboration avec les designers, industrialisation, optimisation des performances… à l’époque j’étais parti du principe que le SDK était mature, que les participants des Techdays y avaient déjà surement jeté un oeil, et qu’il fallait répondre non pas à des débutants, mais plutôt à des développeurs avec des problématiques concrètes sur des projets menés au quotidien. Priorité à la pratique, et au pragmatisme.
Cette année, cette expérience n’a fait qu’augmenter. Il faut donc hausser le niveau d’un cran, tout en proposant à la fois des sujets techniques de tous niveaux, sur les différentes parties du développement d’une application. L’idée du parcours de cette année sera donc “les plus belles applications”. Différentes, bluffantes, inspirantes… je souhaite que chaque développeur, indépendamment de son niveau et de son implication dans Windows Phone 7, ressorte de chacune des sessions auxquelles il assistera avec des étoiles dans les yeux et l’opinion que les applications sont plus belles sur Windows Phone 7 que n’importe où ailleurs.
Le portail de proposition des sessions est déjà ouvert, mais maintenant, vous savez quels seront les ingrédients des sessions qui feront leur chemin dans le parcours Windows Phone 7: n’hésitez donc pas à en proposer plein, et à me faire vos retour d’idées ou de “commandes de sessions” dans les commentaires!
Une petite info est en train de faire le tour de la toile des développeurs Windows Phone ce matin et je ne peux m’empêcher de la relayer, car la question sort assez souvent: il s’agit d’un feature documenté (j’insiste sur l’importance de chacun de ces deux mots) qui permet de passer le codage des couleurs de 16 à 32 bits quand une application en a besoin. Sans plus attendre, le truc:
<App xmlns="" BitsPerPixel="32" ...>
Dans le fichier WMAppManifest.xml de l’application (là ou il y a les capabilities, la définition des background agents etc) il y a une propriété BitsPerPixel de la balise App qui peut prendre les valeurs 16 ou 32 !
Cette fonctionnalité est documentée sur la page de doc qui concerne le manifest de l’application sur MSDN et est disponible uniquement dans Mango (Windows Phone 7.5).
Impacts? plus de “bandes” sur les gros aplats/dégradés de couleurs dans les photos et dans l’application (même si l’utilisation des dégradés est rarement “metro compliant”). Ca peut servir dans certains cas comme les jeux ou les applications à fort caractère graphique. La majorité ne devrait pas en avoir besoin… Et sachez que la différence est largement plus visible sur les écrans AMOLED que Super-LCD…
N’importe quel ingénieur en architecture de microprocesseur vous dira que le passage en 32 bits charge la mémoire, la mémoire graphique, le CPU, le bus du contrôleur vidéo. Ca va donc logiquement impacter la batterie et les performances du téléphone (CPU, mémoire, potentiellement framerate…). Est-ce que l’impact est visible à vous de me le dire, ça dépend du cas d’usage… Quant à la question “pourquoi on est pas en 32 bits par défaut?” la réponse la plus sensée qui me vient en tête (mais je ne suis pas dans le groupe produit…) c’est “parce qu’on en a pas besoin! (merci Metro!)”
Avec Mango, le SDK Windows Phone 7.1 s’est enrichi de 2 launchers à dimension “sociale” : ShareStatusTask, et ShareLinkTask. Ces launchers permettent de publier sur Facebook, Windows Live, Twitter, des statuts ou des liens en utilisant le compte de l’utilsateur configuré sur le téléphone, sans avoir à passer par l’authentification du SDK Facebook. Ca rend le développement de composants sociaux de l’application beaucoup plus simple. Idée du jour: utiliser ces launchers pour que vos utilisateurs puissent partager leur usage de votre application, et le lien vers l’application elle-même !
L’application qui va nous servir d’exemple est Klout Kikimeter. c’est une application que j’ai développé pour cet article (il fallait avoir quelque chose à partager!)… et qui permet de mesurer votre “influence en ligne” en utilisant Klout. Pas de jugement de valeur ici, mais bon, moi j’appelle ça “mesurer son kiki”.
On va voir 2 usages différents de l’application: le premier, le plus simple, partager un statut. le deuxième: partager le lien, et en l’occurrence je vous propose de partager un lien directement vers l’application… L’utilisateur partageant ainsi le lien vers votre application, et ses contacts pourront directement lancer la version web de marketplace sur son PC, ou directement l’application Marketplace du téléphone (qui marche même depuis le hub people!)
Rien de plus facile: il suffit de faire appel à ShareStatusTask, c’est juste 3 lignes de code:
ShareStatusTask sst = new ShareStatusTask(); sst.Status = "Somebody measured a Kiki! " + TwitterScreenName + " scores " + Score.ToString() + " on Klout!"; sst.Show();
Ce qui nous donne l’expérience suivante :
Jusqu’ici c’est enfantin.. la partie suivante se corse un peu: nous allons essayer de partager un hotlink de notre application depuis le téléphone.
Pour cela, on utilise la ShareLinkTask, qui ressemble beaucoup à la ShareStatusTask:
ShareLinkTask slt = new ShareLinkTask(); slt.Title = "klout kikimeter"; slt.Message = "I use this app to measure my kiki online! check it out! "; slt.LinkUri = new Uri("http://www.windowsphone.com/s?appid=2d2a85b1-d6ee-43f4-a302-4ada9fc606ea", UriKind.Absolute); slt.Show();
et voila!
En plus d’un message, vous pouvez voir qu’il faut un lien, et un titre. Hors au moment de la conception de l’application, le deeplink permettant d’ouvrir l’application Marketplace sur la bonne page n’existe pas encore (puisque votre application n’est pas publiée)!
La méthode que j’avais trouvé à l’origine consiste à procéder en 2 temps… Une première publication de l’application, en mode cachée, créera l’ApplicationId qui permettra de publier une mise à jour après… et passer l’application en mode public. Ca crée un petit overhead, mais peu importe, car l’application n’a même pas besoin d’être complète! juste certifiable, car au fur et à mesure des mises à jour l’ApplicationId ne change plus.
Mais depuis, Vivien Chevallier nous a trouvé une idée beaucoup plus propre… la solution sur son blog (un indice: il utilise le fichier WMAppManifest.xml)
Voila, 2 petits trucs rapides pour rendre votre application un peu plus “sociale” et profiter du phénomène des réseaux sociaux pour faire votre pub!
Si vous voulez aller plus loin dans la découverte des réseaux sociaux et de leur usage dans une application mobile, je vous invite également à regarder le webcast de cette session sur les APIs des réseaux sociaux, par Nicolas Humman aux Techdays 2012.
Nokia, qui vient de lancer ses Lumia 710 et 800, se lance dans un roadshow à travers toute la France, pour rencontrer les développeurs mobiles. L’occasion de se rencontrer et de se former en une journée (9h30 – 17h) au développement Windows Phone 7.5.
Les dates!
>> Inscrivez-vous! <<
Cet article est le troisième et dernier d’une série qui constitue une introduction à la réalité augmentée avec Windows Phone 7.5 (Mango). Voici les liens vers les articles précédents:
Dans le dernier article, nous avons appris à récupérer la position de l’utilisateur. Il faut aussi récupérer la position GPS de ces points d’intérêt, mais ce n’est pas le but de cet article que de détailler les API de foursquare. Une fois qu’on a ces coordonnées, il faut commencer par calculer la direction de ces derniers par rapport à la position de l’utilisateur et du téléphone, afin de savoir si on doit les afficher ou pas à l’écran quand l’utilisateur pointe son téléphone dans une direction ou une autre :
foreach (Venue v in ViewModel.Venues) { // Calcul du cap de la Venue en fonction de la position du téléphone double Bearing = Math.Round(ARHelper.CalculateBearing(v.Location, ViewModel.MyLocation ), 0); // Calcul de la position de la Venue en fonction de l'angle, et de la distance, dans le repère XNA Vector3 RelativeVenuePosition = ARHelper.AngleToVector(Bearing, (WCSRadius * v.Distance / Radius)); AddVenue(RelativeVenuePosition, v); }
Le code ci-dessus calcul pour chaque point d’intérêt le cap en fonction de la position de l’utilisateur, et instancie un Vector3 représentant les coordonnées du point d’intérêt relativement à l’utilisateur. C’est de ce point dont on se servira par la suite pour calculer s’il doit être affiché ou pas !
Vous remarquerez que le bout de code ci-dessus utilise quelques méthodes qui ne sont pas dans le framework. Ce sont des méthodes développées pour ne pas charger le code en calculs mathématiques de position : en voici le code :
public static class ARHelper { public static double CalculateBearing(GeoCoordinate Venue, GeoCoordinate MyPosition) { ARHelper.DegreeToRadian(MyPosition.Latitude - Venue.Latitude); double num1 = ARHelper.DegreeToRadian(MyPosition.Longitude - Venue.Longitude); double num2 = ARHelper.DegreeToRadian(Venue.Latitude); double num3 = ARHelper.DegreeToRadian(MyPosition.Latitude); double angle = Math.Atan2(Math.Sin(num1) * Math.Cos(num3), Math.Cos(num2) * Math.Sin(num3) - Math.Sin(num2) * Math.Cos(num3) * Math.Cos(num1)); return ARHelper.RadianToDegree(angle) + 180.0; } public static Vector3 AngleToVector(double inAngle, double inRadius) { double num = ARHelper.DegreeToRadian(inAngle - 90.0); return new Vector3((float)Math.Round(inRadius * Math.Cos(num)), 0.0f, (float)Math.Round(inRadius * Math.Sin(num))); } public static double DegreeToRadian(double angle) { return 3.14159265358979 * angle / 180.0; } public static double RadianToDegree(double angle) { return angle * 57.2957795130823; } }
Il ne reste plus qu’à afficher les points à l’écran ! Comme dit plus tôt, on va rafraichir l’affichage à chaque changement de position du téléphone on va donc écrire le restant du code dans la méthode qui sert de handler pour l’évènement de changement de position du spatial framework. C’est ici qu’on va faire tous les calculs de projection des points sur l’écran, en fonction de leur position relative qu’on a calculé plus tôt. On va donc retrouver toutes nos matrices… Allons-y petit à petit :
D’abord, on calcule la position du téléphone dans l’espace :
Matrix attitude = Matrix.CreateRotationX(MathHelper.PiOver2) * motionReading.Attitude.RotationMatrix;
Le telephone étant tenu horizontalement, il faut appliquer une rotation e 90° sur l’axe des X sur la matrice de position u téléphone qui nous est renvoyée directement par le Spatial Framework.
Ensuite, pour chacun des points, il va falloir créer une matrice qui décrit sa position en 3D dans le repère utilisé pour faire la projection :
Matrix world = Matrix.CreateWorld(points[i], Vector3.UnitZ, Vector3.UnitX);
Il ne reste plus qu’à projeter le point sur l’écran:
Vector3 projected = viewport.Project(Vector3.Zero, projection, view, world * attitude);
Une fois la projection faite, si le point est dans le champ de vision on les affiche, sinon on les masque:
if (projected.Z > 1 || projected.Z < 0) { AugmentedLabels[i].Visibility = System.Windows.Visibility.Collapsed; } else { AugmentedLabels[i].Visibility = System.Windows.Visibility.Visible; // Centrage du contrôle sur le point TranslateTransform tt = new TranslateTransform(); tt.X = projected.X - (AugmentedLabels[i].RenderSize.Width / 2); tt.Y = projected.Y - (AugmentedLabels[i].RenderSize.Height / 2); AugmentedLabels[i].RenderTransform = tt; }
Ici le tableau AugmentedLabels contient les éléments graphiques à afficher (en l’occurrence, un UserControl avec une icône, un nom, et une distance.
Et voilà ! Nous savons maintenant comment marche la réalité augmentée en mode « viewfinder » dans Windows Phone 7.5.
Pour ceux qui souhaiteraient industrialiser leurs développements, il est possible de dériver un petit framework de tout ça… ou alors d’en utiliser un existant, comme celui-ci : Geo Augmente Reality Toolkit.
Vous pouvez retrouver l’intégralité de ce tutorial au format word ainsi que l’application d’exemple sur le skydrive suivant:
Happy coding !
Cet article est le deuxième d’une série qui constituent une introduction à la réalité augmentée avec Windows Phone 7.5.
Voyons maintenant les API qui nous permettent d’utiliser les différents composants utiles du téléphone, à savoir la caméra, le spatial framework qui permet d’avoir la position du téléphone et les APIs XNA permettant de réaliser les transformations.
C’est la partie la plus simple ! en effet avec Mango on dispose de 2 APIs différentes : la classe PhotoCamera, spécifique à Windows Phone, permettant de contrôler la caméra, qu’elle soit frontale ou au dos du téléphone, le flash, le bouton de prise de photo, etc… Une autre API existe, l’API héritée de Silverlight 4, qui garantit la compatibilité avec les applications Desktop, dont nous n’avons cure…
Pour afficher le flux de la caméra à l’écran il faut définir une zone d’affichage, qui sera rectangulaire : pour cela, il suffit de définir un rectangle, dont nous définirons la source du contenu en code :
<Rectangle> <Rectangle.Fill> <VideoBrush x:Name="viewfinderBrush" /> </Rectangle.Fill> </Rectangle>
Et pour le code behind :
PhotoCamera camera = new PhotoCamera(CameraType.Primary); viewfinderBrush.SetSource(camera);
Ces quelques lignes suffisent à afficher le flux vidéo à l’écran. Il faut savoir qu’on peut intercepter chacune des frames de la caméra pour y appliquer un traitement, mais cela ne nous intéresse pas dans cet article. C’est en revanche un élément clef pour l’autre type de réalité augmentée, qui nécessite d’appliquer un traitement à l’image pour détecter un tag par exemple.
Maintenant que nous sommes en mesure d’afficher le code de la camera, il nous faut récupérer la position du téléphone. Pour cela nous allons utiliser un spatial framework, c’est-à-dire une API qui permet d’intégrer les fonctionnalités des différents capteurs du téléphone (accéléromètre, boussole, et éventuellement gyroscope), et d’y appliquer un certain nombre de traitements (des filtres par exemple) pour lisser le signal. Comme tous les capteurs dans Windows Phone 7 ce spatial framework dispose d’une interface asynchrone : on va donc l’instantier, le configurer pour recevoir un certain nombre de positions par seconde, et s’abonner à l’évènement de changement de position, avant de le lancer. C’est dans la méthode associé à cet évènement qu’on écrira le code permettant d’afficher les points d’intérêt à l’écran, étant donné que la position de ces points d’intérêt sur l’écran change en fonction de la position du capteur !
Motion motion = null; if (!Motion.IsSupported) { MessageBox.Show("Motion API Not supported :("); return; } if (motion == null) { motion = new Motion(); motion.TimeBetweenUpdates = TimeSpan.FromMilliseconds(66); // 15 FPS, largement suffisant motion.CurrentValueChanged += new EventHandler<SensorReadingEventArgs<MotionReading>>(motion_CurrentValueChanged); try { motion.Start(); } catch (Exception ex) { MessageBox.Show("Impossible de démarrer l'API Motion! " + ex.Message); } }
Vous remarquerez qu’avant toute chose, on teste si l’API Motion est supportée : en effet, pour fonctionner le spatial framework a besoin au moins d’un accéléromètre et d’une boussole… hors cette dernière est optionnelle dans les chassis Windows Phone 7 (de même que le gyroscope). En tout état de cause, sans boussole, impossible de connaitre l’orientation du téléphone et donc de l’utiliser pour faire de la réalité augmentée… Le gyroscope quand il est présent amène quant à lui une bien meilleure précision dans la captation des mouvements du téléphone.
Notre application exige qu’on connaisse la position de l’utilisateur afin de situer les points d’intérêt autour de lui. C’est chose très facile avec Windows Phone 7, grace à l’API GeoCoordinateWatcher. Encore une fois elle est asynchrone : il faut donc l’instancier, s’abonner à l’évènement de changement de position et démarrer le capteur :
GeoCoordinateWatcher gcw = new GeoCoordinateWatcher(GeoPositionAccuracy.High); if (gcw.Permission == GeoPositionPermission.Granted) { gcw.MovementThreshold = 20; gcw.PositionChanged += new EventHandler<GeoPositionChangedEventArgs<GeoCoordinate>>(gcw_PositionChanged); gcw.Start(); } else { MessageBox.Show("You denied location permission - please enter a location in the textbox"); }
Attention à bien vérifier que l’utilisateur a bien autorisé sa géolocalisation avant !
Nous avons étudié les concepts mathématiques dans la partie 1, les APIs dans la partie 2, il ne reste plus qu’à utiliser tout cela ensemble pour obtenir une application! c’est l’objectif de la partie 3….
Avec la version 7.1 du SDK Windows Phone (Mango), l’intégration de la réalité augmentée dans les applications mobiles va devenir beaucoup plus facile : de nouveaux composants font leur apparition et sont conçus pour rendre très simple le développement de ce type d’interface. Mais d’abord, intéressons-nous à ce qu’est la réalité augmentée, et aux concepts qu’il faut maitriser avant de s’attaquer au code.
Le principe de base de la réalité augmentée est d’afficher en surimpression d’une image « réelle » (issue d’une caméra par exemple) des informations purement digitales. Les deux usages les plus courant de la réalité augmentée sont :
Dans cet série d’articles, nous allons nous intéresser au premier cas d’utilisation, à savoir le téléphone en mode « viewfinder ».
La première chose à comprendre, c’est qu’il nous faut appliquer un certain traitement aux points d’intérêts : en effet nous voulons représenter dans un espace 2D (l’écran du téléphone) un certain nombre d’informations liées à des coordonnées 3D (le monde réel : latitude, longitude, distance, etc). Il faut donc projeter ces points sur l’écran. Et finalement, projeter dans un espace en 2D des points d’un monde en 3D, on le fait depuis longtemps dans le monde des jeux vidéos. On va donc reprendre le vocabulaire de ce monde :
Lorsqu’un objet sera contenu dans le frustum alors il sera projeté sur le viewport dans la direction de l’œil de l’utilisateur.
Pour simplifier un schéma pas forcément trivial, on considérera que l’œil de l’utilisateur est à une distance fixe du viewport, dans l’axe de la normale à son centre (le vecteur qui part du centre du rectangle, perpendiculairement au plan du rectangle). Il n’existe de toute façon pas de moyen simple de savoir où se trouve l’œil de l’utilisateur par rapport à l’écran… A moins de faire du face tracking avec la caméra frontale, ce qui n’est pas l’objectif de cet article.
Tout cela peut se résumer à ce schéma :
Cette projection doit bien entendu également tenir compte de la position du téléphone : en effet, dans une localisation d’un objet en 3D (donc potentiellement, plus haut ou plus bas que l’utilisateur) il faut tenir compte de la position du téléphone (qu’il faut de toute façon connaitre pour savoir dans quel sens l’utilisateur regarde !!).
Pour symboliser la position de la caméra, du téléphone, des points d’intérêt, et les transformations qu’il faut y appliquer pour les projeter sur l’écran, un seul outil… les matrices.
Pas besoin de se replonger dans un cours d’algèbre linéaire pour faire de la réalité augmentée cependant ! il faut juste accepter le fait qu’une position peut être codée par un vecteur 3D (donc composé des 3 coordonnées x, y et z) auquel on adjoint en général une 4ème composante nécessaire pour les calculs qu’on appellera w. Par ailleurs il est possible de coder dans une matrice 4x4 à la fois des rotations, des translations, des mises à l’échelle (scaling), et des projections. Encore une fois pas besoin de comprendre pourquoi et comment ça marche (même si c’est très intéressant… si vous voulez en savoir plus, rendez-vous dans les ressources au bout de ce document, nombre d’articles intéressant y figurent pour avoir plus de contexte).
Si on résume tout ça : je prends des points en 3D symbolisés par des vecteurs, que je multiplie par des matrices symbolisant des transformations, pour avoir de nouveaux vecteurs, dans un repère 2D : l’écran du téléphone. Nous verrons dans la suite que les API de Windows Phone 7 notamment dans XNA permettent de faire ça très simplement.
Le prochain article de cette série sera consacrée aux APIs de Windows Phone dont on va se servir, à savoir la Caméra, la classe Motion (aussi appelée Spatial Framework) et le GPS
Voici les liens vers les articles suivants:
Le 8 novembre, sur le Campus Microsoft à Issy-les-Moulineaux, on vous invite à rencontrer un des “Senior UX Designer” à l’origine de l’interface Metro de Windows Phone 7: Arturo Toledo.
L’agenda est le suivant:
Voila quelques formations au design metro et quelques sessions sur le sujet auxquelles je participe, mais jamais encore je n’avais vu un planning d’évènement sur le sujet aussi bien gaulé (gauler étant un terme technique, bien entendu ). Que vous soyez développeur Windows Phone 7 ou pas, je pense que vous allez en prendre plein la vue… il faudra vraiment y être!
L’inscription c’est par ici!