Olivier Dahan

MVP Client Development 2014
MVP Silverlight 2013, 2012, 2011, 
MVP CAD 2010, MVP C# 2009


Membre du Developer Guidance Advisory Council Microsoft

Audit, Conseil, Formation, Développement
[WPF, Silverlight, WinRT, MonoDroid]

Historique

Stratégie de développement Cross-Platform–Partie 3

Read this article in your language IT | EN | DE | ES
Après avoir présenté la stratégie, sa motivation et ses outils dans la Partie 1, après avoir poser le décor des premiers modules communs dans la Partie 2, il est temps d’aborder la réalisation des modules spécifiques et de voir fonctionner notre applications sur différentes plateformes !

Bref Rappel

 

La stratégie

La Partie 1 expose une stratégie de développement cross-platform et cross form-factor basée sur l’utilisation de C#, .NET, Visual Studio, MonoDevelop, MonoTouch (pour iOS) et MonoDroid (pour Android), le tout avec le framework MVVM cross-platform MVVMCross.
Cet ensemble d’outils permet avec un seul langage de programmation, un seul EDI (pour iOS il faut toutefois utiliser MonoDevelop sous Mac), une seule plateforme (.NET MS et Mono) d’attaquer onze cibles différentes : les smartphones et tablettes Apple, Android, Windows “classique”, Windows 8 (WinRT), Windows Phone...
La stratégie se base aussi sur une séparation particulière du code, l’essentiel de celui-ci n’ayant besoin d’être écrit qu’une seule fois.
On trouve dans la partie immuable à 100% le serveur métier regroupant l’intelligence et le savoir-faire métier. Puis on trouve un projet “noyau” dont la variabilité assumée est très faible puisque tout le code sera simplement lié dans des projets bis visant à produire des binaires spécialisés pour chaque cible.
Dans la partie la plus fluctuante ou la variabilité assumée peut atteindre 100% on trouve bien entendu les différents projets implémentant les Vues de façon spécifique pour chaque cible.
Le maitre mot de la stratégie est “plus le code devient spécifique, moins on écrit de code”. Grâce à cette approche supporter de nombreuses cibles différentes ne coute pas très cher et se limite à la création des Vues sans remise en cause ni des ViewModels ni du code métier.
La concentration du savoir-faire du développeur avec un seul langage, un seul EDI, un seul framework (C#, VS/MonoDevelop, .NET) évite la couteuse mise en place de formations lourdes ou l’embauche de personnel qualifié par cible visée. Tout le monde s’y retrouve, le développeur qui voit son champ d’action élargi donc son travail devenir plus intéressant, le DSI qui ne gère qu’une seule équipe et une formation minimale, l’entreprise qui diminue ses couts de réalisation et de maintenance tout en assurant sa présence sur de multiples médias et cibles.

L’exemple de code

La Partie 2 pose le décor de l’exemple de code utilisé pour illustrer la stratégie proposée.
En repartant d’un exemple datant de 2008 (les codes postaux français) utilisant un service Web et un frontal Silverlight Web nous allons rajouter de nouvelles cibles absolument non prévues à cette époque et ce sans remettre en cause une seule ligne de programmation du serveur métier. Cela démontre au passage l’énorme avantage de cette stratégie qui pérennise le code au travers des années, des modes, et des technologies changeantes.
Après avoir créé la solution VS, y avoir ajouté les librairies MVVMCross, nous créons le ViewModel principal (l’exemple n’aura qu’une page sans navigation pour simplifier).
Ce ViewModel fait partie du projet dit “’noyau”. Il est écrit une fois pour toute et va être utilisé pour créer des projets “bis” ciblant chacun une plateforme spécifique. Il n’y a pas de duplication de code, les projets bis sont créés en ajoutant des liens vers le code du projet noyau original. Seul le fichier projet (csproj) diffère puisque c’est lui qui permet de sélectionner le compilateur à utiliser.
Le projet noyau se voit agrémenté de quelques classes propres au fonctionnement de MVVMCross et des convertisseurs de valeurs qui seront utilisés par les Vues. On utilise ici les mécanismes de MVVMCross qui gomme les nuances entres les OS de façon à pouvoir écrire des convertisseurs qui marcheront aussi bien sous Android que sous WinRT ou Windows Phone sans rien avoir à recompiler ni modifier.

L’étape du jour

Nous possédons le serveur métier (celui des codes postaux), nous possédons le cœur de l’application écrit une seule fois en C# (le noyau). Nous allons écrire maintenant les projets spécifiques à chaque plateforme sélectionnée pour notre exemple.
Se souvenir : Il ne s’agit que d’un exemple simplifié à l’extrême pour ne pas être trop long. Le serveur métier est ici réduit à sa plus simple expression, le noyau ne définit qu’un seul ViewModel, les parties spécifiques ne couvrent que quelques cibles seulement et n’exposent qu’une seule vue. Le lecteur, j’en suis certain, saura transposer cette démonstration simplifiée à des cas plus complexes et plus réels.
A noter: Je ne détaille pas ici tout le fonctionnement de MVVMCross, j’ai prévu d’y revenir dans un article spécialement consacré à cette librairie.

Le Noyau

Je l’ai succinctement décrit dans la Partie 2. Voici son organisation :
image
Il s’agit, pour rappel, d’une librairie de code Android 1.6 afin de se garantir un noyau le plus restrictif possible puisque son code sera ensuite réutilisé partout sans réécriture.
La version d’Android ne compte en réalité pas vraiment puisque le noyau n’utilise pas de spécificités de l’OS cible. C’est la version de .NET Mono qui compte et c’est MonoDroid qui nous fournit le nécessaire (en niveau 3/4 par rapport au framework Microsoft). Nous disposons donc d’un framework puissant gérant, par exemple, Linq et les expressions Lambda...
Le projet déclare un espace de nom CPCross.Core, le projet lui-même portant en suffixe le nom de sa cible (.Android, .NET4 ou .WindowsPhone comme on le voit sur l’image ci-dessus).
On note la présence d’une Référence Web qui point sur le “serveur métier”. Si le code de chaque projet “bis” a été créé par simple lien vers le code du projet noyau, la Référence Web a été ajoutée à chaque projet “manuellement”. Il doit y avoir un moyen d’éviter cette étape mais je n’ai pas eu le temps de le chercher. Cela ne se fait qu’une fois pour chaque projet ce qui n’est pas très contraignant (les mises à jour éventuelles du serveur métier étant prises en compte ensuite par un simple refresh par clic-droit sur la référence).
Comme je l’ai indiqué dans la partie précédente, le seul problème rencontré l’a été avec Windows Phone qui n’accepte pas les Références Web mais qui oblige à utilise une Référence de Service. La classe de service générée ne porte pas le même nom, ce qui a du être pris en compte dans le code du noyau, mais heureusement son fonctionnement reste le même.
Le code du noyau est le suivant :
using System;
using System.Collections.Generic;
#if WINDOWS_PHONE
using CPCross.Core.WindowsPhone.EnaxosCP;
#else
using CPCross.Core.EnaxosCP;
#endif
using Cirrious.MvvmCross.Commands;
using Cirrious.MvvmCross.ViewModels;


namespace CPCross.Core.ViewModels
{
public class MainViewModel : MvxViewModel
{
#if WINDOWS_PHONE
private EnaxosFrenchZipCodesSoapClient service;
#else
private EnaxosFrenchZipCodes service;
#endif
private readonly List<string> errors = new List<string>();
private string searchField;
private bool isSearching;
private bool sortByZip;
private bool searchAnyWhere;

public MainViewModel()
{
// commanding
ClearErrorsCommand = new MvxRelayCommand(clearErrors, () => HasErrors);
GetCitiesByNameCommand = new MvxRelayCommand(getCitiesByName, () => !IsSearching);
GetCitiesByZipCommand = new MvxRelayCommand(getCitiesByZip, () => !IsSearching);


// init service
createService();
}

//add error to error list
private void addError(string errorMessage)
{
errors.Add(errorMessage);
FirePropertyChanged("Errors");
FirePropertyChanged("HasErrors");
ClearErrorsCommand.RaiseCanExecuteChanged();
}

private void clearErrors()
{
errors.Clear();
FirePropertyChanged("Errors");
FirePropertyChanged("HasErrors");
ClearErrorsCommand.RaiseCanExecuteChanged();
}


private void createService()
{
try
{
#if WINDOWS_PHONE
service = new EnaxosFrenchZipCodesSoapClient();
#else
service = new EnaxosFrenchZipCodes();
#endif
service.FullVersionCompleted += service_FullVersionCompleted;
service.FullVersionAsync();
}
catch (Exception ex)
{
addError(ex.Message);
}
}

private void service_FullVersionCompleted(object sender, FullVersionCompletedEventArgs e)
{
if (e.Error == null)
{
Title = e.Result.Title;
Version = e.Result.Version;
Description = e.Result.Description;
Copyrights = e.Result.Copyrights;
WebSite = e.Result.WebSite;
Blog = e.Result.Blog;
MiniInfo = e.Result.Version + " " + e.Result.Copyrights;
FirePropertyChanged("Title");
FirePropertyChanged("Version");
FirePropertyChanged("Description");
FirePropertyChanged("Copyrights");
FirePropertyChanged("WebSite");
FirePropertyChanged("Blog");
FirePropertyChanged("MiniInfo");
return;
}
addError(e.Error.Message);
}


// search by name
private void getCitiesByName()
{
if (!canSearch()) return;
IsSearching = true;
service.GetCitiesByNameCompleted += service_GetCitiesByNameCompleted;
service.GetCitiesByNameAsync(searchField, searchAnyWhere, sortByZip);
}

private void service_GetCitiesByNameCompleted(object sender, GetCitiesByNameCompletedEventArgs e)
{
InvokeOnMainThread(() => IsSearching = false);
service.GetCitiesByNameCompleted -= service_GetCitiesByNameCompleted;
if (e.Error == null)
{
InvokeOnMainThread(() =>
{
Result = e.Result==null ? null : new List<CityObject>(e.Result);
FirePropertyChanged("Result");

});
return;
}
InvokeOnMainThread(() =>
{
addError(e.Error.Message);
Result = new List<CityObject>();
FirePropertyChanged("Result");
});
}


// search by zip
private void getCitiesByZip()
{
if (!canSearch()) return;
IsSearching = true;
service.GetCitiesByZipCompleted += service_GetCitiesByZipCompleted;
service.GetCitiesByZipAsync(searchField, sortByZip);
}

private void service_GetCitiesByZipCompleted(object sender, GetCitiesByZipCompletedEventArgs e)
{
InvokeOnMainThread(() => IsSearching = false);
service.GetCitiesByZipCompleted -= service_GetCitiesByZipCompleted;
if (e.Error == null)
{
InvokeOnMainThread(() =>
{
Result = e.Result==null ? null : new List<CityObject>(e.Result);
FirePropertyChanged("Result");
});
return;
}
InvokeOnMainThread(() =>
{
addError(e.Error.Message);
Result = new List<CityObject>();
FirePropertyChanged("Result");
});
}

// internal test
private bool canSearch()
{
return !IsSearching && !string.IsNullOrWhiteSpace(searchField);
}

// service information
public string Title { get; private set; }
public string Description { get; private set; }
public string Version { get; private set; }
public string Copyrights { get; private set; }
public string WebSite { get; private set; }
public string Blog { get; private set; }

public string MiniInfo { get; private set;}

// errors
public List<string> Errors
{
get
{
return errors;
}
}

public bool HasErrors
{
get
{
return errors.Count > 0;
}
}

// search result
public List<CityObject> Result { get; set; }

// search field
public string SearchField
{
get
{
return searchField;
}
set
{
if (searchField == value) return;
searchField = value;
FirePropertyChanged("SearchField");
}
}

// result sort
public bool SortByZip
{
get
{
return sortByZip;
}
set
{
if (sortByZip == value) return;
sortByZip = value;
FirePropertyChanged("SortByZip");
}
}

// search mode
public bool SearchAnyWhereInField
{
get
{
return searchAnyWhere;
}
set
{
if (searchAnyWhere == value) return;
searchAnyWhere = value;
FirePropertyChanged("SearchAnyWhereInField");
}
}

// search state
public bool IsSearching
{
get { return isSearching; }
private set
{
if (isSearching == value) return;
isSearching = value;
FirePropertyChanged("IsSearching");
GetCitiesByNameCommand.RaiseCanExecuteChanged();
GetCitiesByZipCommand.RaiseCanExecuteChanged();
}
}

// commanding
public MvxRelayCommand ClearErrorsCommand { get; private set; }
public MvxRelayCommand GetCitiesByNameCommand { get; private set; }
public MvxRelayCommand GetCitiesByZipCommand { get; private set; }
}
}
Ce code est réellement très classique pour un ViewModel suivant le pattern MVVM. On y trouve bien entendu des petites spécificités propres à MVVMCross mais quand on pratique de nombreux frameworks MVVM on s’y retrouve facilement, tous ne font que régler les mêmes problèmes de façon finalement assez proche.
Des propriétés et des commandes. Voilà ce qu’est un ViewModel dans la pratique. C’est bien ce que reflète le code ci-dessus.
Lorsque le ViewModel est instancié il fait un premier appel au service pour aller chercher les informations de base retournées par ce dernier (version, copyrights, etc).
Ensuite les recherches sont effectuées à la demande via les commandes exposées.
Le ViewModel expose même une liste des erreurs qui stocke les exceptions éventuelles. Dans notre exemple ne mettant en œuvre qu’une seule Vue sans navigation nous n’utiliserons pas ce mécanisme. Le code source étant publié en fin d’article, à vous de jouer et d’ajouter ce qu’il faut pour afficher les erreurs s’il y en a (il y a une commande ClearErrors qui peut être bindée à un Button pour effacer la liste).

Le serveur métier

Le serveur métier est généralement un service Web (ou un ensemble de services web) le plus standard possible.
Ici j’ai repris le serveur d’une démo écrite en 2008 pour Silverlight. Il marche depuis cette époque sans aucun soucis (sur une base de données Access/Jet ... c’est pour dire à quel point ce n’est pas un code moderne !).
C’est un simple '”asmx” bien loin de sophistications parfois inutiles de WCF. Sa simplicité lui donne l’avantage de l’universalité, et le fait que je puisse aujourd’hui le réutiliser sans changer une ligne de code prouve de façon éclatante que la stratégie proposée permet réellement de pérenniser le code en le protégeant des modes et des technologies clientes changeantes.
le WDSL nous indique ceci :
image
S’agissant d’un exemple, ce “serveur métier” contient peu de “savoir métier” voire aucun... Mais il pourrait contenir des centaines de services sophistiqués, cela ne changerait rien à notre stratégie. Dans un tel cas il serait d’ailleurs certainement segmenté en plusieurs services différents tournant éventuellement sur des machines différentes.
L’exemple multi-plateforme n’utilisera d’ailleurs qu’une partie des services exposés.

Une première cible : Windows “classique” en mode Console

Il est temps d’ajouter une première cible “visuelle”, un client spécifique.
J’ai choisi avec prudence Windows “classique” avec un mode désuet mais très pratique pour m’assurer que tout fonctionne : la console !
Certes cela fait très MS-DOS, mais cela prouve que MVVMCross couvre vraiment tous les cas de figure même les plus étranges et surtout cela m’assurait de pouvoir déboguer le noyau sans aucune complication liée à Android ou Windows Phone ou autre plateforme. Bien entendu cela montre malgré tout une première cible très différente des autres et il faut voir ici la console Win32 comme le symbole de tout ce qui peut être fait sous Windows “classique” comme WPF par exemple (Windows XP, Vista, 7, Windows 8 classic desktop).
Tous les projets d’UI portent le même nom que le projet principal avec le suffixe correspondant à leur cible. Ainsi, ce projet s’appellera CPCross.UI.Console.
Tous ajoutent en référence les librairies spécifiques de MVVMCross (ici Cirrious.MvvmCross.Console), et tous pointent la version du noyau qui correspond à leur cible. Ici CPCross.Core.NET4.
image
L’image ci-dessus nous montre l’arborescence de la solution au niveau du sous-répertoire de solution “UI”. On y trouve les projets d’UI dont le projet console.
Tous les projets ont la même structure. On trouve ainsi un répertoire Views qui contiendra l’unique Vue de notre application. On note aussi la présence de deux classes Program.cs et Setup.cs, propres au mode choisi (la console .NET 4) et à MVVMCross (le setup qui initialise l’application et la navigation vers la vue de départ).
La programmation de la vue en mode Console est très classique : des Console.Writeln(xxx) se trouvant dans une méthode appelée automatiquement par MVVMCross à chaque modification du ViewModel (Binding minimaliste) ce qui permet de rafraichir l’affichage, les saisies se faisant via une méthode spéciale aussi qui transforme les saisies clavier en appel aux commandes du ViewModel ou en modification des propriétés de ce dernier.
Le code cette “vue” un peu spéciale est le suivant :
using System;
using System.Collections.Generic;
using System.Text;
using CPCross.Core.ViewModels;
using Cirrious.MvvmCross.Console.Views;

namespace CPCross.Console.Views
{
class MainView : MvxConsoleView<MainViewModel>
{
protected override void OnViewModelChanged()
{
base.OnViewModelChanged();
ViewModel.PropertyChanged += (sender, args) => refreshDisplay();
refreshDisplay();
}

public override bool HandleInput(string input)
{
switch (input)
{
case "n" :
case "N" :
if (ViewModel.GetCitiesByNameCommand.CanExecute())
ViewModel.GetCitiesByNameCommand.Execute();
return true;
case "z" :
case "Z" :
if (ViewModel.GetCitiesByZipCommand.CanExecute())
ViewModel.GetCitiesByZipCommand.Execute();
return true;
case "c" :
case "C" :
if (ViewModel.Result!=null) ViewModel.Result.Clear();
if (ViewModel.ClearErrorsCommand.CanExecute()) ViewModel.ClearErrorsCommand.Execute();
refreshDisplay();
return true;
case "s" :
case "S" :
ViewModel.SearchAnyWhereInField = false;
refreshDisplay();
return true;
case "a" :
case "A" :
ViewModel.SearchAnyWhereInField = true;
refreshDisplay();
return true;
default :
if (input.Trim().ToUpper().StartsWith("SET ") && input.Length > 3)
{
ViewModel.SearchField = input.Substring(3).Trim();
return true;
}
break;
}
return base.HandleInput(input);
}

private void refreshDisplay()
{
System.Console.BackgroundColor = ConsoleColor.Black;
System.Console.ForegroundColor = ConsoleColor.White;
System.Console.Clear();
System.Console.ForegroundColor = ConsoleColor.Yellow;
System.Console.WriteLine("CP-Cross / .NET 4.0 Console UI");
System.Console.WriteLine();

System.Console.ForegroundColor = ConsoleColor.Gray;
System.Console.WriteLine(ViewModel.Title + " - Version: " + ViewModel.Version);
System.Console.WriteLine(ViewModel.Copyrights);
System.Console.WriteLine(ViewModel.Description);
System.Console.WriteLine();


System.Console.ForegroundColor = ConsoleColor.White;
System.Console.WriteLine("Search by <N>ame, by <Z>ip. <SET> {text} to set search term. <C>lear result."+Environment.NewLine+"<S>tarts with or <A>nywhere");
System.Console.WriteLine();
System.Console.ForegroundColor = ConsoleColor.Cyan;
System.Console.WriteLine("Current Search Term : "+(string.IsNullOrWhiteSpace(ViewModel.SearchField)?"<none>":ViewModel.SearchField) +" - Current option: "+(ViewModel.SearchAnyWhereInField?"Anywhere":"Starts with"));
System.Console.ForegroundColor = ConsoleColor.White;
System.Console.WriteLine();

if (ViewModel.IsSearching)
{
System.Console.ForegroundColor = ConsoleColor.Red;
System.Console.WriteLine("Searching...");
return;
}

if (ViewModel.Result!=null && ViewModel.Result.Count>0)
{
System.Console.ForegroundColor=ConsoleColor.Green;
var limit = Math.Min(10, ViewModel.Result.Count);
for(var i =0;i<limit;i++)
System.Console.WriteLine(ViewModel.Result[i].City+"; "+ViewModel.Result[i].ZipCode+"; "+ViewModel.Result[i].DepartmentName);
}
if (ViewModel.Result!=null && ViewModel.Result.Count>10)
System.Console.WriteLine("... ("+ViewModel.Result.Count+" results. 10 firsts displayed)");
if(ViewModel.Result==null || (ViewModel.Result!=null && ViewModel.Result.Count==0))
{
System.Console.ForegroundColor=ConsoleColor.Blue;
System.Console.WriteLine("<aucun résultat>");
}

if (ViewModel.Errors.Count>0)
{
System.Console.ForegroundColor=ConsoleColor.DarkRed;
foreach (var error in ViewModel.Errors)
{
System.Console.WriteLine("- "+error);
}
}

System.Console.WriteLine();
System.Console.ForegroundColor=ConsoleColor.White;
System.Console.Write("Command ? ");
System.Console.ForegroundColor=ConsoleColor.Yellow;
}
}
}
On voit que ce code est une ‘vraie’ vue qui descend de MxConsoleView, ce qui permet à MVVMCross de gérer la dynamique des changements de propriétés et les saisies clavier de façon transparente. Rappelez-vous, notre ViewModel qui est derrière n’a absolument pas connaissance de cette utilisation qui en est faite...
Le couplage Vue/ViewModel est assuré par MVVMCross de façon automatique (la vue déclare dans son entête de classe qu’elle est liée à MainViewModel). Il reste possible comme avec tous les frameworks MVVM un peu sophistiqués de redéfinir les routes entre Vues et ViewModel, ici nous utilisons le comportement par défaut.

Le film...

Il est intéressant de voir tout cela en action. Quelques captures d’écran feront l’affaire et seront plus rapides à commenter qu’une capture vidéo :
image
Etape 1 : l’application s’affiche. Pendant un cours instant les informations en début de page affichent les valeurs par défaut initialisées par le code (partie supprimée de la copie du code ci-dessus). Puis, une fois que le service a répondu la page se met à jour et affiche les informations du Service à jour. On note le copyright de 2008.
S’agissant d’un mode console j’ai prévu un jeu de commande minimaliste : <N> pour chercher par nom, <Z> par zip (code postal), la commande SET <texte> permet de saisir la valeur à chercher. <C> pour clear (effacer le dernier résultat). Les commandes <A> et <S> permettent d’indiquer si le terme saisi doit être cherché en début de chaine uniquement ou n’importe où dans le nom (en mode Zip seul le début de chaine est contrôlé).
L’application affiche le terme courant (en ce moment <none>, aucun), ainsi que les options sélectionnées (recherche en début de chaine).
Il n’y a aucun résultat (pourquoi c’est en français ? une incohérence de ma part, je fais tout en anglais mais le naturel reviens parfois à la sournoise !).
L’application est en attente de commande. Il va falloir saisir un terme à chercher.
image
En tapant la commande “set xxxx” j’initialise la variable de recherche du ViewModel. Les lettre “orl” sont tapées.
image
Le terme a été validé, on voit dans la ligne turquoise que le “Current Search Term” est bien égal à “orl”. Je tape la commande “n” pour recherche par Nom.
image
La commande “N” vient d’être validée. Le ViewModel expose un booléen “IsSearching” qui est exploité ici pour afficher le texte rouge “Searching...”. Nous exploitons bien toutes les possibilités du ViewModel, même dans sa dynamique...
image
 
Le résultat s’affiche... Nom de la ville, code postal, département. Pour que cela tienne en une page, seuls les 10 premiers résultats sont affichés ce que nous rappelle la dernière ligne verte (en indiquant que l’application à trouvé 12 réponses).
On peut bien entendu faire la même chose avec un code postal partiel ou complet.
Ce qui est intéressant ici est bien entendu le fait d’avoir un frontal .NET 4 pour Windows classique qui symbolise tous les frontaux qu’on peut écrire pour cette cible en natif comme WPF. Rares seront les applications qui utiliseront le mode Console, j’en suis convaincu n’ayez crainte Sourire

Une Seconde Cible : Android

Me voici rassurer sur le fonctionnement du “noyau”, tout se déroule comme prévu. Le serveur est distant (en Angleterre) nous sommes bien dans une configuration “réelle”.
Il est temps de sortir MonoDroid de sa cachette...
C’est très simple, puisqu’il est installé sur ma machine, sous Visual Studio je n’ai qu’à créer un nouveau projet... pour Android. Aussi compliqué que cela !
Voici la structure du projet :
image
 
Je n’entrerai pas dans les détails d’un projet MonoDroid ni même dans les arcanes du développement sous Android, cela s’écarterait bien trop du sujet.
On retrouve ici tout ce qui fait un projet Android de ce type : des Assets, des ressources dont notamment “Layout’ qui contient les fichiers Axml qui définissent les Vues. On trouve aussi, comme sous Windows Phone, la notion de Splash Screen affiché automatiquement pendant le chargement de l’application ainsi que la classe “Setup” qui fait partie du mécanisme de MVVMCross.
Côté références, le projet se comporte comme le précédent : il pointe la librairie MVVMCross adaptée à la cible (Cirrious.MvvmCross.Android) ainsi que le noyau ad hoc (CPCross.Core.Android).
Le répertoire Views fait partie du mécanisme MVVMCross : les vues sont décrites par une classe que le framework peut retrouver facilement, le code ne faisant rien de spécial en général à ce niveau :
using Android.App;
using Cirrious.MvvmCross.Binding.Android.Views;
using CPCross.Core.ViewModels;
using Cirrious.MvvmCross.Converters.Visibility;

namespace CPCross.UI.Android.Views
{
[Activity]
public class MainView : MvxBindingActivityView<MainViewModel>
{
protected override void OnViewModelSet()
{
SetContentView(Resource.Layout.Main);
}
}
}
L’attribut d’activité est propre à l’OS Android (il faut voir cela comme une tâche, un processus). Le code se borne donc à spécifier la vue qu’il faut charger (Resource.Layout.Main).
image
Ci-dessus on voit l’écran SplashScreen en cours de conception via le designer visuel Android qui s’est intégré à Visual Studio.
Ci-dessous l’écran principal avec le même designer visuel, sous MonoDevelop (on ne peut pas voir la différence sur cette capture il faudrait voir les menus pour s’apercevoir que le look est légèrement différent de VS) :
image
On retrouve ici tout ce qui fait une application Android, le format téléphone (on peut choisir bien entendu n’importe quelle résolution), des messages affichés, une case à cocher, des bouton, une zone la liste résultat, un indicateur d’attente, etc.
La liste n’est pas une simple Listbox mais une liste fournie par MVVMCross pour être bindable. Il n’y a pas de Binding sous Android sauf si on ruse un peu...
D’ailleurs pour ceux qui maitrisent déjà Xaml, comprendre Axml (humm le nom est vraiment proche !) n’est pas sorcier, la preuve, voici le code de cette page :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:text="CPCross / Android UI"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/textView1"
android:textColor="#ff808080" />
<TextView xmlns:local="http://schemas.android.com/apk/res/CPCross.UI.Android"
android:text="Small Text"
android:textAppearance="?android:attr/textAppearanceSmall"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/txtInfo"
android:textColor="#ffc0c0c0"
local:MvxBind="{'Text':{'Path':'MiniInfo'}}" />
<LinearLayout
android:orientation="horizontal"
android:minWidth="25px"
android:minHeight="25px"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/linearLayout1">
<CheckBox xmlns:local="http://schemas.android.com/apk/res/CPCross.UI.Android"
android:text="Search anywhere"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:id="@+id/cbAnyWhere"
local:MvxBind="{'Checked':{'Path':'SearchAnyWhereInField'}}" />
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:minWidth="25px"
android:minHeight="25px"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/linearLayout2">
<TextView
android:text="Search term"
android:textAppearance="?android:attr/textAppearanceSmall"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:id="@+id/textView2"
android:gravity="center" />
<EditText xmlns:local="http://schemas.android.com/apk/res/CPCross.UI.Android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/edSearchTerm"
android:layout_marginLeft="8px"
local:MvxBind="{'Text':{'Path':'SearchField'}}" />
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:minWidth="25px"
android:minHeight="25px"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/linearLayout3">
<Button xmlns:local="http://schemas.android.com/apk/res/CPCross.UI.Android"
android:text="Search Name"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:id="@+id/btnSearchName"
local:MvxBind="{'Click':{'Path':'GetCitiesByNameCommand'}}" />
<Button xmlns:local="http://schemas.android.com/apk/res/CPCross.UI.Android"
android:text="Search Zip"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:id="@+id/btnSearchZip"
local:MvxBind="{'Click':{'Path':'GetCitiesByZipCommand'}}" />
<ProgressBar xmlns:local="http://schemas.android.com/apk/res/CPCross.UI.Android"
android:text="Search Zip"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:id="@+id/progressBar1"
android:indeterminate="true"
android:indeterminateBehavior="cycle"
android:indeterminateOnly="true"
android:layout_gravity="center_vertical"
android:layout_marginLeft="30px"
local:MvxBind="{'Visibility':{'Path':'IsSearching', 'Converter':'Visibility'}}" />
</LinearLayout>
<Mvx.MvxBindableListView xmlns:local="http://schemas.android.com/apk/res/CPCross.UI.Android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
local:MvxBind="{'ItemsSource':{'Path':'Result'}}"
local:MvxItemTemplate="@layout/listitem_viewmodel" />
</LinearLayout>
Vous remarquerez la similitude avec Xaml. Et vous noterez l’espace de nom “local” dans une tradition identique à celle qu’on utilise sous Xaml pour référencer du code interne à l’application. Ici cela sert à accéder aux facilités, aux extensions fournies par MVVMCross pour le Binding.
La Syntaxe du Binding MVVMCross tente d’être la plus proche possible de celle de Xaml en utilisant une sérialisation JSon.
Par exemple, la Checkbox est liée ainsi :
local:MvxBind="{'Checked':{'Path':'SearchAnyWhereInField'}}"
C’est à dire que la propriété Checked est liée à la propriété SearchAnyWhereInField, déclarée dans le ViewModel du projet noyau. Le ViewModel est le datacontext implicite de cette vue, MVVMCross s’en charge. Cette notion est exactement la même qu’en Xaml, une fois encore...
Plus subtile est la liaison de l’indicateur busy :
local:MvxBind="{'Visibility':{'Path':'IsSearching', 'Converter':'Visibility'}}"
Ici c’est la propriété Visibility (même nom, décidément !) de la ProgressBar qui est liée à la propriété IsSearching du ViewModel.
Mais rappelez-vous... Cette propriété est un booléen, et autant sous Xaml que sous Android “Visibility” est d’un autre type (différent dans les deux environnements en plus). Comment régler ce problème très courant ?
Si vous avez suivi, vous vous rappelez que le projet noyau déclare un convertisseur qui utilise les facilités de MVVMCross. Et bien c’est ici qu’il est utilisé.
Dans la version Android, notre convertisseur, écrit une seule fois, saura fournir la valeur attendue par visibility, et sous Windows Phone, que nous verrons plus loin, le même binding sur la même propriété du ViewModel utilisera le même convertisseur du noyau qui cette fois-ci retournera l’énumération attendue par Xaml ...
C’est peu de chose au final, très peu même. Mais quelle chose ! Grâce à de petites astuces de ce genre nous avons écrit un seul code pour toutes les plateformes. Et cela n’a pas de prix. Enfin, si, un cout énorme, l’écriture à partir de zéro plusieurs fois de la même application un coup en C#, un coup en Objective-C, l’autre en Java Android, etc... Si vous tentez un léger calcul le gain réalisé par l’approche que je vous propose est démoniaque ! (les bonnes âmes peuvent me verser 10% du montant économisé pour mes bonnes œuvres, je saurai les utiliser à bon escient Sourire).
Est-ce que ça marche ?
image
 
Oui ça marche !
Et bien même.
Ici l’ensemble des résultats est bien entendu accessible, on peut scroller avec le doigt si nécessaire.
La mise en page n’est pas géniale, ce n’est qu’un exemple ne l’oublions pas (parmi plein d’autres).
Les plus curieux se demandent peut-être comment la liste est mise en page ... Comme on sait le faire en Xaml en créant un DataTemplate, ici on créé une Vue spéciale qui ne fait que la mise en page d’un Item, en gros cela marche de la même façon que Xaml donc... Le StackPanel n’existe pas sous ce nom, mais on le trouve sous l’appellation LinearLayout sous Android. Il peut être en orientation verticale ou horizontale, la même chose... Dans ce LinearLayout j’ai placé deux TextBlock, heu non, pardon, deux TextView (très difficile à se rappeler aussi ...) en utilisant la même technique de Binding de MVVMCross.
Dire que cela a été compliqué à faire serait mentir. Il y a bien deux ou trois choses sur lesquelles on bute, mais grâce à Internet on trouve vite les réponses. Et surtout grâce à MonoDroid et MVVMCross qui à eux deux rendent le développement Android presque identique à du Xaml...

Une troisième Cible ?

Allez, pour la route, vous en reprendrez bien une dernière ?
C’est un développement réellement cross-platform, avec un seul code écrit une fois : le serveur métier totalement universel, les ViewModels totalement portables avec MVVMCross.
Rajouter une nouvelle cible me prend juste du temps, mais j’en ai, je suis en vacances ! (même si je vois mal la différence avec d’habitude).
Je vais ainsi ajouter une version Windows Phone. J’aime bien Windows Phone.
D’abord c’est du Silverlight, et en plus c’est vraiment un super OS très différent de Android ou iOS qui se battent en procès en s’accusant d’imitation alors que l’un comme l’autre n’ont fait que reprendre l’idée d’un bureau avec des icônes. Windows Phone au moins ne copie personne. Et pour avoir à la fois un téléphone Android (Sony Xperia Arc S) et un Windows Phone (Nokia Lumia 800), je peut affirmer que j’aime les deux, mais qu’au niveau fluidité et interface j’ai un gros faible pour le Lumia et l’OS Microsoft. Windows Phone n’a pas encore eu le succès qu’il mérite, mais sincèrement je vous invite à le tester vous serez agréablement surpris.
Je vais écourter un peu la démo car ce billet est très long déjà (et avec toutes les captures parfois ça passe mal entre Windows Live Writer et mon serveur où se trouve Dot.Blog).
Ajouter un projet sous VS vous savez le faire. Choisir un projet Windows Phone dans la liste (une fois le SDK installé) n’est pas très compliqué non plus.
image
 
La structure est celle d’un projet Windows Phone, avec son SplashScreen, son App.xaml, sa MainPage.xaml etc.
Pour que le service Web fonctionne j’ai du recopier dans le projet “ServiceReferences.ClientConfig” qui a été généré automatique dans le projet “noyau” Windows Phone après l’ajout du service Web. Bien que référencé, il ne semble pas que le projet Windows Phone trouve ce fichier. La copie est en réalité un lien (comme on le voit à la petite flèche), il n’y a donc toujours pas de duplication de code.
Je passe la mise en page Xaml, le Binding qui cette fois-ci est totalement naturel, la création d’un vrai DataTemplate pour la liste des résultats, etc.
Est-ce que ça marche ?
Aussi !
image
 
C’est pas magique tout ça ?

Conclusion

Voilà... Je vous ai présenté “ma stratégie” de développement cross-platform / cross form-factor.
C’est simple, ça n’utilise qu’un seul langage de programmation, qu’un seul framework, .NET, qu’un seul EDI (VS ou MonoDevelop sa copie), une seul framework MVVM qui en plus efface les petites différences entre les plateformes, et surtout des outils géniaux comme MonoTouch ou MonoDroid.
La stratégie repose aussi sur la pérennisation du code métier dans un ou plusieurs services Web, rendant l’approche encore plus rentable et plus ouverte à toute adaptation.
L’exemple présenté n’est qu’une simple démonstration sans fioriture, mais c’est un vrai projet multi-plateforme, multi OS, multi form factor avec un vrai service distant, des écrans qui font un vrai travail. Il n’y aucune différence avec une application réelle autre que le contenu et la complexité de celui-ci.
L’écriture des 3 parties du billet m’a prise plus de temps que l’écriture de la solution fonctionnelle... 4 jours contre 3. Et si mon expérience de C# et Xaml est reconnue, je ne connais pas encore la même “intimité” avec Android. Et pourtant je l’ai fait (avec plus de soucis en réalité sous Windows Phone que sous Android ce qui est un comble).
Ma stratégie est simple et utilise le moins de “bidules” ou de “trucs” annexes. C’est vraiment le moyen le plus simple de faire du cross-plateforme aujourd’hui pour qui connait C# et Xaml, j’en suis convaincu.
Ca marche, aucun code n’est dupliqué, seule les parties fortement variables sont redéveloppées (les UI) et c’est normal mais peu contraignant puisqu’on travaille en MVVM et que tout est dans les ViewModels... Poser quelques Checkbox ou TextView ou TextBlock n’est franchement pas bien compliqué.
Cette approche minimise les couts, à la fois de développement et de maintenance, mais aussi, les plus chers, ceux du personnel compétent à former pour gérer autant de cibles. Ici cela se résume au strict minimum.
Bien maitriser C#, .NET et MVVM est la base non négociable. Le reste ce n’est que du Lego, normalement un vrai plaisir pour tout ingénieur qui à l’âme d’un ingénieur... C’est à dire pour celui qui aime autant les théories que les rendre tangibles, palpables en réalisant quelque chose qui marche et qui sera utile.
Espérant que ce billet en trois volet aura lui aussi été utile aux lecteurs de Dot.Blog,
A la prochaine (j’en plein de trucs en réserve!) donc...
Stay Tuned !
 
Le code source de la solution (sans MVVMCross), nécessite VS 2010 + derniers patches, le SDK Windows Phone, MonoDroid avec les SDK Android :
Nota: Le web service des codes postaux sert de test, vous pouvez y accéder pour tester la solution mais soyez sympa, n’en abusez pas, si le serveur venait à être gêné par le trafic généré je serai obligé de couper le web service. Merci d’avance !


Comments (21) -

Nk54
Nk54
8/27/2012 10:23:33 AM

Ce sujet fait partie du top 3 de tes meilleurs articles Smile

1) Jounce
2) celui ci
3) j'hésite entre tous les behaviors et mef et mvvm ^^

un grand merci il me tarde de tester tout cela ! Smile

Olivier
Olivier
8/27/2012 4:23:38 PM

@nk54: merci. L'hésitation prouve au moins qu'il y a le choix Smile

Phobias
Phobias
8/28/2012 5:43:42 AM

Et bien, je me sens comblé comme après un film d'Hitchcock au cinéclub...

Si je devais également jouer à faire un top de tes meilleurs articles, celui-ci serait certainement dans le trio de tête.

Mais je ne jouerai pas à ce jeu ! Tout d'abord car je suis mauvais joueur et surtout car ça mettrait en concurrence des dizaines d'excellents billets. (billets dans lesquels je me plonge encore régulièrement)

Certains ont le Leblanc sur leur table de chevet, moi c'est le Dot.Blog. Smile

Par contre là où je n'hésiterai à placer ce billet en tête, c'est dans le top des connaissances qu'un développeur doit absolument posséder pour être efficace professionnellement.

Je n'ai aucun doute sur le fait que trouver un emploi dans le secteur du développement me sera bien plus aisé une fois ce bagage maitrisé.

Tout simplement Merci.

J&#233;r&#244;me
Jérôme
8/28/2012 11:29:45 AM

démo testée, grandement appréciée, approuvée...
La facilité avec laquelle il est possible de cibler WP7, Android,... et win7/win8 nous ouvre des portes incroyables! Sûrement un tournant important dans la société dans laquelle je travaille.  
Un grand merci!

Olivier
Olivier
8/28/2012 4:03:39 PM

@Jerome: ceux qui ont tout lu ont un avantage sur les autres : ils savent que c'est possible et que c'est pas si compliqué !
Maintenant il ne faut pas gommer les petits trucs de ci de là à savoir sur chaque plateforme, notamment si on veut utiliser le hardware. Mais pour des applis LOB telles qu'on en écrit tous les jours, c'est presque aussi facile que dans la démo.

Olivier
Olivier
8/28/2012 4:10:26 PM

@Phobias: merci de ton soutien Smile

La récompense pour tout ce temps passé à écrire des billets c'est de savoir que certains lecteurs ont réellement appris quelque chose qui leur sera utile.

Merci de me permettre de le savoir Smile



olivier dufour
olivier dufour
8/29/2012 8:39:50 AM

/me est content de voir le succes de xamarin (en tant que contributeur mono depuis bien longtemps)

Je préfère juste monodevelop a visual car on a les designers pour android et iphone...

Sylvain
Sylvain
8/29/2012 8:58:03 AM

Fabuleux !!

Olivier
Olivier
8/29/2012 6:51:04 PM

@Olivier dufour: dans les versions actuelles, au moins de MonoDroid, on a le designer visuel sous Visual Studio (parfois il ne s'affiche pas et je n'ai pas bien compris pourquoi, mais sinon ça marche très bien, pareil que sous MonoDevelop).

Nk54
Nk54
8/31/2012 5:00:06 PM

Je viens de terminer l'article de 54 pages (nombre de page parfaite et je dis pas ça parceque mon pseudo est nk54 :p)

Et franchement ? GRACIAS !!! Woaw, tes articles me surprennent de plus en plus. Je te dois plus de la moitié de ma veille techno du monde .NET. Et je parle même pas des compétences que j'ai acquis/amélioré au fil de tes articles.

Donc un grand bravo pour ton article.

Je me demande juste comment se passerai l'intégration des tests unitaires, pour tout ce qui est dév spécifique avec gestion des templates, visualstates, navigation avec MVVMCross ... A voir les parties qui peuvent s'écrire à coup de template T4 ...

Je pense qu'une fois au point, ça doit envoyer du lourd de chez lourd !

J'espère avoir le loisir de pousser les tests sous peu. Mais lorsque ce sera le cas, je te donnerai des nouvelles.

En attendant je veillerai régulièrement à l'arriver de l'article sur MVVMCross. Je m'en régale d'avance à l'idée de te lire Smile

Cordialement, Nk54

Nk54
Nk54
8/31/2012 5:01:28 PM

oops ... Encore la mauvaise habitude de pas me relire ... Donc ça sera avec les fautes d'orthographes. (A quand la possibilité d'éditer un post ? :p)

boblemar
boblemar
9/4/2012 12:19:25 PM

Super ! Un peu long, mais bon... au moins l'architecture est bien détaillée. Pas besoin de lire le code pour comprendre.

J'ai toutefois une petite question :
Dans ta stratégie, tu comptes n'utiliser que MVVMX même pour des projets mono-target, au cas où... ou bien tu comptes utiliser d'autres frameworks ?
En d'autres termes, j'ai un client pour qui je dois créer une appli WPF, mais peut-être que dans 3 mois, ils vont me dire : "tiens, j'ai des tablettes, ça serait bien que ça marche dessus non ?".
Est-ce que ça vaut le coup de se concentrer sur MVVMCross dans une éventualité faible de cible multiple, ou d'autres frameworks seront plus efficaces ?

Olivier
Olivier
9/5/2012 3:57:52 AM

@Nk54: merci Smile
On peut faire du unit testing, c'est quelque chose que je n'ai pas encore investigué car je suis toujours en train d'étudier le source (vu qu'il n'y a pour ainsi dire aucune doc) pour écrire mon papier.
Mais je sais que c'est possible. Comme le projet noyau est identique pour toutes les cibles, je pense même qu'on peut ne tester qu'une version, sous WinRT ou Windows Phone par exemple.
Mais je vais m'y intéresser et je tiendrai les lecteurs de Dot.Blog au courant !

Olivier
Olivier
9/5/2012 4:00:14 AM

@nk54: je sais c'est un peu ch* de ne pas pouvoir modifier un commentaire... même en mode admin je ne peux pas. Quand j'ai vraiment fait de trop grosses fautes, je copie, je détruis et je recolle dans un nouveau commentaire... Mais ce n'est possible qu'en mode admin.
Je suis en train de mettre en place une nouvelle version du blog avec les dernières versions de blogengine, je vais regarder (un truc de plus à "regarder" !) pour l'édition des commentaires parce qu'est ça serait mieux en effet.

Olivier
Olivier
9/5/2012 4:14:16 AM

@boblemar: l'article détaille beaucoup la stratégie car c'était finalement ça le sujet central. Mais l'exemple prend beaucoup de place aussi, du coup courrir deux lièvres à la fois faisait un peu long... Mais au moins si c'est clair c'est le principal !
Pour l'instant je ne connais que MvvmCross qui soit multi plateforme avec le support de nombreux services liés aux plateformes comme passer un appel téléphonique, accéder aux fichiers, etc. C'est difficile à trouver car il faut un développeur qui connaisse toutes les cibles... Le travail de Stuart Lodge à cet égard me semble assez unique.
MvvmCross est encore très jeune et par exemple la cible WPF n'existe pas officiellement, ce n'est pas très compliqué de l'ajouter en partant de la cible Windows Phone à mon avis mais ce n'est pas encore fait. J'en ai parlé à Stuart et je n'ai pas encore sa réponse sur ce point. Mais c'est qqchose que je vais développer comme extension au framework certainement si lui ne le prend pas en charge.
La question de savoir si pour un projet WPF il faut partir avec MvvmCross est donc pour l'instant pas tout à fait claire puisque la cible WPF n'existe pas.
Mais si on fait abstraction de ce problème qui finira par être réglé, et pour répondre à ta question, je pense en effet que même un projet simple, disons aujourd'hui une app Windows Phone, il faut utiliser MvvmCross pour se préparer. Ajouter une cible Android ou iOS sera très facile. Et si cela ne se fait pas, MvvmCross est tout de même à la base un bon framework MVVM.
Avec le temps, et je l'espère l'adoption assez large de ce framework, beaucoup de choses vont s'améliorer (messaging, support de WPF, utilisation de PCL qui évite d'avoir plusieurs projets dupliqués pour le noyau, etc).
Mais en ce moment, dans le marché tel qu'il est, c'est à dire totalement imprévisible, travailler avec MvvmCross c'est se protéger et protéger son travail, donc ça me parait un choix sage, les trois billets sur le sujet montrent que j'en fais plus qu'un simple choix de lib MVVM, c'est une stratégie de dev à part entière où MvvmCross n'est qu'un morceau du tout.

boblemar
boblemar
9/5/2012 2:28:10 PM

Merci pour ta réponse.
Le fait que WPF ne soit pas supporté m'avait totalement échappé, mais à mon avis, effectivement, ça ne devrait pas durer....

Nk54
Nk54
9/12/2012 5:35:12 PM

J'avance bien ! la config de l'environnement est super simple

Par contre, juste pour info : ton service web est inaccessible : http://www.e-naxos.com/enaxosfrenchzipcodes.asmx Redirection vers http://www.e-naxos.com/FileNotFound.htm?aspxerrorpath=/enaxosfrenchzipcodes.asmx

J'ai regardé au niveau de PCL, Stuart reste quand même scotché. Et il ne sait pas quand il avancera car il lui faut un update dans monotouch et il ne sait pas quand Xamarin fera la maj. Donc ça fait +/- 4 mois qu'il est coincé. Mais je ne désespère pas. Les cores unifié est assez séduisant. Mais ce n'est pas si contraignant qu'on pourrait croire.

Je prépare un petit document sur l'installation de l'environnement et la création de la partie data (web service etc). Je te l'enverrai une fois finit.

Sinon, à la compilation j'ai une erreur :
The type or namespace name 'List' could not be found (are you missing a using directive or an assembly reference?) (pour le projet Core.WindowsPhone). Je précise que j'ai bien Cirrious.MvvmCross.WindowsPhone dans les références.

J'ai peut être sauté une étape. Je vais relire pour vérifier.

L'EDI monodevelop est bluffant ! Vu la taille de l'exécutable, je m'attendais à une version ultralight de visual studio. Que nenni ! Cet EDI n'a pas a rougir devant visual studio (ok, je n'ai pas tester 10 % de monodevelop, mais un rapide tour sur les menus rassure). Un point négatif tout de même, sur mon PC pro, il plante au lancement : MonoDevelop.Ide.TypeSystem.TypeSystemService' threw an exception.

@Olivier: Pour les commentaires, ce n'est pas moi que ça gène mais plutôt les fou de l'orthographe et de la grammaire qui doivent saigner des yeux en me lisant Smile

Nk54
Nk54
9/12/2012 5:52:56 PM

Edit :

J'ai trouvé d'où vient l'erreur : mauvais toolkit de windows phone. Retélécharger la dernière version a corriger les erreurs que j'avais.

(je note dans mon fichier !)

Lolocas
Lolocas
10/9/2012 10:19:36 AM

Juste pour signaler à la suite de ce fabuleux article que Stuart Slodge a porté MVVMCross sur PCL (portable class library) et qu'il a fait une branche vnext de son code source pour qu'on puisse tester.

http://slodge.blogspot.fr/2012/09/mvvmcross-vnext-portable-class.html

Yannick
Yannick
3/3/2013 2:22:54 PM

Hello,

Moi aussi j'ai été emballé par l'article ...

Mais en tentant de faire une petite application on déchante très vite.

En effet, le pseudo SDK MvvmCross est tout sauf compréhensible dans le sens ou on ne sait pas quoi référencer dans sa solution.

Et en plus lorsqu'on ouvre la solution télécharger sur Github il n'y a pas mois de 815 erreurs et 174 avertissement ...

Après dans votre article vous dites que vous partez des sources pour ne pas être embêter mais sachez que votre .zip ne fonctionne pas car il ne trouvent plus les fameuses sources.

Certe je n'ai pas un niveau MVP ou autre mais je m'en sort bien avec MVVM Light et je ne pensais pas être tant bloqué avec MvvmCross ...

Cela semble prometteur mais ces barrières risquent de vite tuer ce projet ...

En conclusion, on ne sait pas quoi faire avec ce SDK ...

Aucune explications ne permet de démarrer correctement.

Cordialement

Phobias
Phobias
5/22/2013 2:26:46 PM

Bonjour Olivier,

Je me suis mis à développer un Sudoku Cross Platform et il semble que l'architecture du framework ait beaucoup changé avec le passage à la v3.

Impossible de trouver comment mettre en place une console.
IMvxMessagePump existe toujours par contre je n'ai trouvé aucune référence à IMvxServiceConsumer dans les sources de MVX.

J'ai également relevé qu'une classe IMvxConsoleNavigation existe.

Une piste existe peut être à travers les lignes suivantes :
IMvxConsoleCurrentView starter = new MvxConsoleMessagePump();
starter.CurrentView = new MainView();

Je tente de creuser de ce côté là mais pour l'instant rien de concret.

Le passage à la V3 a amené un code bien plus maintenable mais hélas il ne se fait pas sans heurt.

Bonne journée

Pingbacks and trackbacks (1)+

Add comment

  Country flag

biuquoteurl
  • Comment
  • Preview
Loading