Intermédiaire

SoMood – Les interactions

uxL’art du développement d’une application de qualité passe par une bonne interaction avec l’utilisateur. En effet, un utilisateur peut vite se retrouver perdu sans indicateur de l’état de l’application, sans message lui communiquant une erreur (ou un succès), ce qui peut rapidement amener à une mauvaise expérience utilisateur.

Dans le cadre de l’application SoMood, nous avons bien entendu veillé à fournir les indicateurs nécessaires (comme les messages d’erreurs, les fenêtres de chargement) et, grâce à Xamarin couplé à certains de ses composants, nous avons pu proposer une gestion des messages d’information à la fois efficace côté développement et intuitive/agréable côté utilisateur. Après la sociabilisation de SoMood, il est maintenant temps de parler des messages d’information, et c’est tout de suite que cela se passe !

 

L’importance de communiquer avec l’utilisateur

 

Durant l’utilisation d’une application mobile, il est très fréquent d’effectuer certaines interactions : une connexion, un ajout d’élément ou un envoi de message/image. Quand l’action s’est terminée (avec succès ou non), il paraît normal, en tant qu’utilisateur, d’avoir un retour pour connaître l’état de celle-ci. De même, tant que l’action n’est pas terminée (connexion en cours par exemple), un indicateur de chargement est généralement apprécié (la base, me direz-vous). Mais est-ce si évident dans la pratique ? Eh bien, pas tant que ça. Il est encore possible, et pas si rare, de trouver des applications mobiles dont l’expérience utilisateur est catastrophique, voire inexistante. Ce qui est dommage, c’est que ces utilisateurs sont justement ceux qui vont télécharger l’application, la noter, lui permettre de vivre, et s’ils ne sont pas satisfaits… Celle-ci part à la poubelle et peut se voir attribuer un commentaire salé sur le store.

lukeNOOOOOO

Du coup, on peut facilement comprendre pourquoi l’utilisateur est au cœur des préoccupations lors du développement d’une application mobile.

 

Afficher les messages : ce qu’apporte Xamarin

 

Une façon simple et largement utilisée pour afficher des informations à l’utilisateur, est la fenêtre de type popup. Sous iOS, cela s’appelle une AlertView, sous Android une Dialog, et sous Windows Phone une MessageBox.

Bien évidemment, c’est la méthode basique pour afficher un message, mais les possibilités sont nombreuses. Par exemple, sous Windows Phone, une MessageBox sera plutôt affichée en cas d’erreur requérant une attention totale de l’utilisateur ou une action. Si l’erreur ne bloque pas l’expérience (ou pour un message d’information quelconque), le développeur préférera souvent l’utilisation des Toasts, moins intrusifs pour l’utilisateur (exemple ci-dessous sous forme de bandeau).

cr5Un

 

Finalement, étant donné que ce n’est que de l’affichage, on peut se demander en quoi Xamarin intervient dans l’histoire.

Tout réside dans le découpage de la solution, et l’utilisation du projet Core. Dans un projet qui utilise le design pattern MVVM, toute la logique métier est dans la partie Core, partagée par chaque plateforme. C’est grâce au regroupement de points communs aux trois applications dans ce projet que Xamarin va encore une fois prouver son efficacité dans le cadre du développement d’applications mobiles cross-platform.

Après toute cette théorie, il est temps de passer à la partie pratique, en reprenant le cas de SoMood. Dans un premier temps, nous allons parler des messages d’erreurs.

 

Le découpage

 

Avant de commencer, il est important de rappeler que, dans le développement d’une application Xamarin, même si on essaye de faire en sorte de regrouper le plus possible de code métier dans le projet commun, chaque interface graphique est propre à sa plateforme. Cela va se traduire par le déclenchement de l’action d’affichage d’une popup dans un ViewModel (partie commune), mais l’implémentation de cet affichage sera dans la partie spécifique à chaque OS. Ce qui justifie de l’abstraire via de l’injection de dépendance.

Quand on parle d’injection de dépendance, la première chose qui vient à l’esprit est le mot « interface ». On va justement se servir d’une interface qui, si vous avez bien suivi, sera présente dans le projet Core, et de trois implémentations différentes, une dans chaque projet mobile.

L’avantage de cette méthode est d’un côté un découpage clair des différentes couches, une factorisation de la gestion des erreurs côté métier, ainsi qu’une interface graphique conforme à chaque plateforme, facilement modifiable selon les besoins.

 

La construction

 

Dans le projet commun

Pour la gestion des messages d’erreur, dans le projet Core, l’interface est très simple et possède une seule méthode : Show (qui comme son nom l’indique va déclencher l’affichage du message). Elle utilise un ToastType, une énumération qui va permettre de spécifier le type de message à afficher. Cela permettra, du côté des vues, de définir un code couleur par type.

public interface IToastService
{
    void Show(string title, string message, ToastType type);
}

public enum ToastType
{
    Success,
    Error,
    Neutral
}

Ensuite, comme tout autre service du projet SoMood.Core, l’interface va être injectée depuis le constructeur du ViewModel dans lequel elle va être utilisée, et ce de manière transparente pour le développeur. On ne se soucie pas encore de comment l’interface va être implémentée sur iOS, Android et Windows Phone.

L’utilisation du service est par conséquent très simple. Par exemple, dans le cas de la méthode de Login, on va vouloir afficher un message en cas d’erreur. Dans le try catch qui englobe l’appel au service de connexion, plus précisément dans le catch, il suffit d’appeler la méthode Show de notre IToastService, en précisant le titre, message et type (ici, une erreur).

public async Task<bool> Login(string username, string password)
{
    var isLogged = false;
    BusyCounter++;
    try
    {
        await _authenticationService.Login(username, password);
        var user = await _authenticationService.GetUserInfo();
        isLogged = true;
    }
    catch (Exception)
    {
        _toastService.Show("erreur", "Connexion i mpossible. Veuillez vérifier vos identifiants", ToastType.Error);
    }
    finally
    {
        BusyCounter--;
    }
    return isLogged;
}

On pourrait même faire de nouveau appel à notre service Toast pour afficher un message en cas de réussite de connexion mais, dans notre cas, si l’utilisateur est bien connecté, il est directement redirigé vers la page des groupes, et donc n’a pas besoin d’autre indicateur pour savoir que tout s’est passé sans encombres.

Comme je le disais plus haut, grâce à l’injection de dépendance, on a pu se servir de l’interface IToastService sans se soucier de son implémentation sur chaque plateforme, ni de l’affichage final, un avantage de ce design pattern souvent apprécié par les développeurs.

 

Dans les projets iOS, Android et Windows Phone 8.1

Du côté de chaque projet mobile client, on va trouver une classe ToastService implémentant IToastService, et c’est le Bootstrapper, la classe qui regroupe la gestion de notre conteneur IoC (Autofac), qui, au moment de l’initialisation du conteneur IoC, va chercher à récupérer la bonne version du ToastService et l’instancier (voir ci-dessous).

builder.RegisterType ().AsSelf ().As ().SingleInstance ();

La classe correspondante à l’implémentation de IToastService va être trouvée dans le bon namespace grâce aux directives de compilation #if ANDROID et #elif IOS (voir ci-dessous).

#if __ANDROID__
using SoMood.Droid.Helpers;
#elif __IOS__
using SoMood.iOS.Helpers;
#endif

Dans chacune des classes ToastService, la méthode Show va pouvoir faire appels aux composants natifs de la plateforme correspondante pour afficher des messages. Petite précision : le terme « composant natif » ne veut pas nécessairement dire qu’il faut impérativement se servir d’une AlertView en iOS ou d’une Dialog sous Android. Ces composants graphiques sont certes très présents et familiers aux utilisateurs, mais rien n’empêche le développeur de créer son propre composant graphique personnalisé (à condition qu’il respecte les guidelines de la plateforme) ou encore d’en utiliser un existant sur le net, à condition qu’il soit libre d’utilisation bien sûr.

C’est justement ce que nous avons choisi de faire pour l’application iOS. En effet, parmi la large sélection de composants Xamarin iOS, on trouve Progess HUD, un composant graphique qui permet, entre autres, un affichage plus sympathique des messages d’information/erreurs.

 

Son utilisation est très simple et, pour preuve, voici l’implémentation de la méthode Show du ToastService sous iOS :

public class ToastService : IToastService
{
    public static UIColor SuccessColor { get; set; }
    public static UIColor ErrorColor { get; set; }
    public static UIColor ForegroundColor { get; set; }

    public void Show(string title, string message, ToastType type)
    {
        var window = UIApplication.SharedApplication.KeyWindow;
        var hud = new MTMBProgressHUD(window);
        hud.Mode = MBProgressHUDMode.Text;
        hud.LabelText = title;
        hud.DetailsLabelText = message;
        hud.RemoveFromSuperViewOnHide = true;

        if (type == ToastType.Success)
            hud.Color = SuccessColor;
        else if (type == ToastType.Error)
            hud.Color = ErrorColor;

        hud.DetailsLabelColor = ForegroundColor;
        hud.LabelColor = ForegroundColor;

        window.AddSubview(hud);

        hud.Show(true);
        hud.Hide(true, 5);
    }
}

Ici, on récupère la fenêtre de l’application, puis on instancie un objet de type MTMBProgressHUD (qui vient donc du composant), on définit le mode de notre toast (dans notre cas, cela sera en mode texte, mais il existe d’autres modes, dont un qui permet de customiser la vue). Ensuite on donne le titre et le message du toast, on lui assigne une couleur, spécifiée en fonction du type de message. Encore quelques petites lignes de réglages graphiques, et enfin on ajoute cet objet à la fenêtre de l’application, on la montre, et on détermine sa disparition automatique au bout de 5 secondes.

En action, cela donne ça :

IMG_4519

Styling-snackbar-1-1024x477

 

Même principe sous Android : on crée la classe ToastService qui implémente IToastService, mais cette fois-ci on va utiliser un composant déjà existant sur Android pour l’affichage des messages, la Snackbar (voir ci-contre). Cette barre s’affiche en bas de l’écran, et son utilisation est encore plus simple que le composant sur iOS :

 

 

public class ToastService : IToastService
{
    public static Color SuccessColor { get; set; }
    public static Color ErrorColor { get; set; }
    public static Color ForegroundColor { get; set; }

    public void Show(string title, string message, ToastType type)
    {
        var navigationService = Bootstrapper.Container.Resolve<NavigationService>();
        var parentLayout = navigationService.CurrentActivity.Window.FindViewById<View>(Android.Resource.Id.Content);

        var snack = Snackbar.Make(parentLayout, message, Snackbar.LengthLong);
        var snackBarView = snack.View;

        if (type == ToastType.Success)
            snackBarView.SetBackgroundColor(parentLayout.Resources.GetColor(Resource.Color.primary));
        else if (type == ToastType.Error)
            snackBarView.SetBackgroundColor(parentLayout.Resources.GetColor(Resource.Color.error));
        
        snack.Show();
    }
}

On récupère, grâce au NavigationService, la vue en cours, on crée la SnackBar avec, entre autres, le message, le type (ici, un texte long, mais on peut avoir un texte cours ou indéfini), on détermine une couleur de fond à la vue de la SnackBar et enfin on l’affiche. Ici, pas besoin de définir un temps de disparition, il est géré par défaut par la Snackbar.

 

Enfin, sous Windows Phone 8.1, c’est directement la classe Shell, implémentant IToastService, qui récupère un ensemble de méthodes déléguées par l’App.Xaml.cs, notamment la navigation et la gestion du bouton back. Du côté du Xaml (Shell.xaml) contenant la frame de l’application, on va rajouter un ItemsControl ayant pour nom ToastContainer. Ce sera donc le conteneur de Toast, avec une couleur de fond en fonction du type de toast, et le texte du message.

<Grid>
    <Frame x:Name="_navigationFrame" />
    <ItemsControl x:Name="ToastContainer">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Border Padding="10">
                    <Border.Background>
                        <Binding Path="Type">
                            <Binding.Converter>
                                <converters:NotificationTypeToBrushConverter
                                    InformationBrush="YellowGreen"
                                    WarningBrush="DarkOrange"
                                    ErrorBrush="Red" />
                            </Binding.Converter>
                        </Binding>
                    </Border.Background>
                    <TextBlock Text="{Binding Path=Message}"
                               FontSize="20"
                               TextWrapping="Wrap"/>
                </Border>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</Grid>

De retour dans le code behind de la vue Shell, la méthode Show va simplement assigner le type et le texte au ToastContainer. On initialise ensuite un timer qui va faire disparaître le toast au bout de 5 secondes, et enfin, on lance le toast ainsi que le timer.

public async void Show(string title, string message, ToastType type)
{
    await ShowNotificationAsync(message, type);
}

public async Task ShowNotificationAsync(string message, ToastType type = ToastType.Neutral)
{
    var o = new { Message = message, Type = type };
    ToastContainer.Items.Add(o);

    var tcs = new TaskCompletionSource<object>();

    var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(4) };
    timer.Tick += (sender, args) =>
    {
        ToastContainer.Items.Remove(o);
        timer.Stop();
        tcs.SetResult(new object());
    };
    timer.Start();

    return tcs.Task;
}

 

L’affichage des temps de chargement

 

Outre les messages d’erreur, il est important de signaler à l’utilisateur lLed1UOlorsqu’il est en train d’attendre la fin d’une action. Typiquement, dans SoMood, la connexion (classique ou via Facebook) peut prendre quelques secondes, tout comme la création de compte, l’ajout d’une image pour un groupe ou pour son profil. Autant d’actions qui demandent du temps et de la patience côté utilisateur, qui doit pouvoir savoir si l’action est toujours en cours ou s’il peut continuer à naviguer sur l’application.

 

Pour afficher cela, on utilise la plupart du temps des animations de chargement (la roue infinie, la barre de chargement, le pourcentage qui augmente…). Dans le cadre de SoMood, nous avons bien sûr fait appel à des éléments de ce type pour notifier l’utilisateur. Le composant ProgressHUD, qui est utilisé pour l’app iOS, en plus d’afficher les messages d’erreur, sert aussi à fournir la popup avec le rond infini que nous connaissons tous. Petite remarque : une simple popup avec marqué « Chargement… » suffit, si on ne souhaite pas s’embêter.

Pour déclencher l’affichage de cette popup, on utilise une propriété de base de chaque ViewModel, IsBusy, qui permet de savoir si une opération est en cours ou non. Avant chaque appel à un service, on va incrémenter un compteur (BusyCounter), qu’on décrémente quand le résultat de l’appel est arrivé. La propriété, de type booléen, IsBusy vérifie uniquement que le compteur soit égal à 0, ce qui signifie qu’il n’y a plus de retour de service en attente.

Du côté des applications clientes (iOS, Android ou Windows Phone) on s’abonne simplement au PropertyChanged du ViewModel courant et, si la propriété du ViewModel modifiée est IsBusy, on affiche ou non la popup qui indique un chargement. L’exemple ci-dessous illustre le code du fichier LoadingHelper du projet SoMood.iOS :

public class LoadingHelper
{
    private readonly MTMBProgressHUD _loadingHud;
    private readonly ViewModel _viewModel;

    /// Initializes the loading HUD, should be instanciated within the ViewDidLoad
    public LoadingHelper(UIViewController controller, ViewModel viewModel, string loadingText = "Chargement...", UIColor backgroundColor = null, UIColor foregroundColor = null)
    {
        _viewModel = viewModel;
        
        _loadingHud = new MTMBProgressHUD(controller.View);
        _loadingHud.LabelText = loadingText;
        
        if (backgroundColor != null)
        _loadingHud.Color = backgroundColor;
        if (foregroundColor != null)
        _loadingHud.DetailsLabelColor = foregroundColor;
        
        controller.View.AddSubview(_loadingHud);
        
        if (_viewModel.IsBusy)
        _loadingHud.Show(animated: true);
        
        _viewModel.PropertyChanged += ViewModel_PropertyChanged;
    }

    void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        if (e.PropertyName == "IsBusy")
        {
            if (_viewModel.IsBusy)
                _loadingHud.Show(animated: true);
            else
                _loadingHud.Hide(animated: true);
        }
    }
}

Et voilà le résultat en pratique (sous forme d’une image cela rend moins bien, il faut donc faire appel à son imagination) :

IMG_4518

La question qui peut se poser serait sur l’implémentation du LoadingHelper : pourquoi l’avoir faite sur chaque plateforme ? Etant donné que les popup de chargement sont uniquement du visuel, nous avons fait le choix de rester cohérents et de garder tout ce qui concerne le contenu lié aux vues dans chaque projet client. Cependant, il aurait été possible de porter une partie du code du côté des ViewModel, en reprenant le même principe que le ToastService.

La pertinence de cette refactorisation serait cela dit moindre, puisque cela forcerait le développeur à jongler entre les différentes couches pour un comportement uniquement visuel. Ce choix dépend de la stratégie adoptée lors de la définition de l’architecture du projet et des pratiques de code adoptées, ce qui le rend totalement subjectif.

 

Conclusion

 

Ce nouvel article sur l’application SoMood a exposé les méthodes qui ont été utilisées durant le projet pour réaliser de bonnes interactions avec l’utilisateur, avec l’exemple des messages d’erreurs et des temps de chargement. Que ce soit avec l’utilisation des composants de base disponibles sur les différents OS, ou la personnalisation de composants pour avoir une identité forte, il est possible de gérer facilement cet aspect, encore plus grâce à Xamarin. Il n’y a donc plus d’excuse pour ne pas le faire !

Je précise pour conclure que les solutions exposées ne se veulent pas être les meilleures solutions, mais elles apportent notre retour d’expérience, et j’espère que cela pourra vous être utile.

 

tumblr_nzotqaEc0d1ue8rilo1_r3_500

Nombre de vue : 135

COMMENTAIRES 1 commentaire

  1. Devanlay dit :

    Bravo

AJOUTER UN COMMENTAIRE