
Photo de Istvan SZEKANY
Les utilisateurs de vos applications sont aussi vos clients. Ils sont très critiques sur les applications qu'ils utilisent et n'hésitent jamais à noter négativement une application qui les a déçu. Il existe bon nombre de bonnes pratiques qui sont importantes à respecter pour avoir l’application la plus réactive et la plus stable possible. Voici donc un échantillon des bonnes pratiques les plus importantes pour le développement d'une application mobile.
Utilisation du cache :
La 4G est sûrement présente dans votre téléphone, vous possédez le dernier modèle de votre constructeur favori, un smartphone surpuissant disposant de toute les dernières fonctionnalités à la mode. Cependant, vous restez un cas rare. Un smartphone peut représenter un budget très important et le pourcentage de la population disposant d’un téléphone dernier cri est loin d’être élevé. Votre application ne pourra donc pas utiliser autant de ressources que sur votre téléphone et le résultat pourrait être sensiblement différent. De même qu'elle est soumise au lieu où celle-ci est lancée (pas de wifi, et peu ou pas de réseau) et quelqu'un peut choisir de l'utiliser pour récupérer une information, qu’il a peut-être déjà vu. Vous devez alors gérer le fait que l’application ne plante pas, que l’utilisateur à accès à tout ce qu’il est possible de lui offrir. Le cache est donc important, il optimise également la vitesse de chargement pour des données qui ne changent pas. Dans ce cas où l’utilisateur ne dispose pas d’internet, la sauvegarde du résultat est une fonctionnalité précieuse :
private Dictionary<string, string> _contentCache = new Dictionary<string, string>();
private string GetAsyncUrl(string url)
{
using (HttpClient client = new HttpClient())
{
try
{
string content = client.GetStringAsync(url).Result; // Get the response
if (string.IsNullOrEmpty(_contentCache[url]))
_contentCache.Add(url, content);
return content;
}
catch (Exception e) // Catch exception if no internet connection / website unreachable.
{
return _contentCache[url] != null ? _contentCache[url] : "";
}
}
}
{
using (HttpClient client = new HttpClient())
{
try
{
string content = client.GetStringAsync(url).Result; // Get the response
if (string.IsNullOrEmpty(_contentCache[url]))
_contentCache.Add(url, content);
return content;
}
catch (Exception e) // Catch exception if no internet connection / website unreachable.
{
return _contentCache[url] != null ? _contentCache[url] : "";
}
}
}
Dans le cas où la requête est répétée :
client.DefaultRequestHeaders.CacheControl.NoCache = false;
Ce paramètre existe par défaut sur HttpClient. Mais pour contrôler ce cache avec plus de précision, vous pouvez ajouter des paramètres à l’url pour que celle-ci change et récupère de nouvelles données.
Imbrication d'éléments graphiques :
Lors de la création des vues sur Android comme sur iOS, la création de éléments imbriqués les uns dans les autres peuvent avoir un effet de plus en plus négatif sur les performances. Le mieux étant sur iOS de plutôt jouer avec les contraintes pour alléger la vue de ses nombreux éléments. Sur Android, il s’agit de la même chose, tout en évitant le plus possible l’imbrication de LinearLayout qui peut largement alourdir l’interface et ses performances.
Outlets/Actions :
Dans iOS, l’abonnement aux méthodes natives des boutons peuvent se faire de différentes manières. La plupart du temps, par méconnaissance et/ou habitude, les développeurs vont utiliser l’association par code du bouton et de sa méthode click :
...
button.TouchUpInside += Button_TouchUpInside;
}
button.TouchUpInside += Button_TouchUpInside;
}
void Button_TouchUpInside(object sender, EventArgs e)
{
}
{
}
Bien que cette manière de faire soit juste, elle n’est pas forcément optimale car, en l’état, en affichant la page plusieurs fois, l’abonnement au bouton se multiplie et créer une fuite mémoire. L’effet est encore pire avec l’utilisation d’une expression lambda.
Xamarin permet pourtant l’utilisation des Outlets (un lien vers un élément graphique) et des Actions (un lien vers une évènement d'un élément graphique) dans le développement des applications iOS. Les abonnements créés par ce biais sont automatiquement détruits lorsque l’utilisateur navigue vers une autre page. Pour cela, il faut utiliser XCode, et ouvrir le fichier xib ou storyboard où se trouve votre élément.

Il faut ensuite cliquer sur le bouton de l'assistant de l'éditeur parmi les trois tout en haut à droite de l’IDE. Cela permet d’ouvrir alors une fenêtre de code suffixé “.m”. Ce fichier permet alors d’ajouter directement des Actions sur les contrôles. Il suffira ensuite d’appuyer sur un bouton, de maintenir la touche “Ctrl” et de faire un clic puis de faire un glisser/déposer de l'élément vers le fichier ouvert.

A noter qu’il est également possible de diriger une action vers une autre déjà créée pour les faire toutes les deux pointer sur la même méthode. Enfin, il arrive qu’une application plante lors d’un appui sur un bouton si l’action de celui-ci n’est pas écrite dans la classe qui est sensé la référencer.
partial void Demo_Clicked(NSObject sender)
{
...
}
{
...
}
Il faut donc tâcher à bien écrire chaque méthode dont l’Action est référencée quitte à n’écrire son code que plus tard.
XAML Compilé :
Ajouté depuis Xamarin Forms 2.0, la compilation du XAML, aussi appellée XAMLC, a permet d’augmenter de manière notable les performances des applications Xamarin Forms., Elle permet d’être notifié dès la compilation des erreurs dans le XAML plutôt qu’à son exécution. Elle permet également de réduire le poids des applications et enfin il supprime les chargements et les initialisations des éléments XAML.
Désactivée par défaut, la mettre en place est assez simple. Il suffit de placer un attribut dans la code-behind de la page. A noter qu'il y a également la possibilité d'appliquer ceci à toute l'application directement via un attribut global :
using Xamarin.Forms.Xaml;
...
[XamlCompilation (XamlCompilationOptions.Compile)]
public class MainPage : ContentPage
{
...
}
Abonnements (Cas des reusableCell) :
Il n’est pas toujours possible de passer par des Actions sur iOS pour s’abonner à des événements. C’est le cas par exemple des ReusableCells. Dans un exemple concret, une cellule peut contenir une méthode dans laquelle il est nécessaire de s’abonner pour réaliser une action. Or avec le scroll, la méthode permettant de créer ou d'update une cellule va être appelée plusieurs fois, l'abonnement se fera à plusieurs reprises et l’action exécutée autant de fois qu'elle contiendra d'abonnements. Ainsi la résolution se passe par ce biais :
cell.Selected -= Cell_Selected;
cell.Selected += Cell_Selected;
cell.Selected += Cell_Selected;
Bien que coûtant une ligne de code supplémentaire, le désabonnement permet à une cellule ne pas appeler les méthodes auxquelles elle a pu être déjà abonnée.
Sur Android et iOS, ceci est également valable lors du changement de pages pour se désabonner des méthodes pour éviter les doubles clicks et les fuites mémoires.
IDisposable :
Contrairement à de nombreux langages tel que le C, en .Net, le développeur n’a pas à gérer la durée de vie de ses objets. Le garbage collector passe régulièrement pour récupérer la mémoire utilisée une fois l’objet inutilisé. Cependant, certains objets ne sont pas détruits dans l’instant, et peuvent garder une trace en mémoire. Dans la mobilité, où les ressources sont réduites, l’utilisation trop élevée de la mémoire peut avoir un impact lourd sur les performances de l’application. Heureusement, certains objets du Framework .Net sont « disposables » et sont alors détruits une fois leur champ d’utilisation passé. Pour cela rien de compliqué :
using(var client = new HttpClient())
{
...
}
...
}
Dans cet exemple, la variable client sera détruite et recyclée à la fin de l’accolade et la mémoire ainsi économisée. De nombreuses fuites mémoires peuvent provenir de l’oubli ou de la méconnaissance de ce type de sucre syntaxique.
CancellationToken :
Les méthodes asynchrones peuventElles donnent également l’occasion de pouvoir effectuer un grand nombre de traitement en arrière-plan et ainsi de pouvoir donner des informations “en temps réel” à l’utilisateur. Mais sur un téléphone portable, de nombreux événements extérieurs peuvent perturber les traitements écrits dans vos méthodes. Ainsi, il peut arriver que le réseau se fasse plus rare ou disparaisse complètement. Si un bouton « Rafraîchir » était mis à disposition, l’utilisateur pourrait être tenté de recharger les données sans que cela ne soit forcément possible ou que la précédente méthode ne soit terminée.
Les CancellationToken, sont des moyens qui permettent à une tâche asynchrone de pouvoir être annulée en cours de traitement. Ainsi, le contrôle d'une tâche permet d’éviter de trop nombreux appels ou d’arrêter ceux qui ne peuvent aller au bout (perte de réseau). Cela permet d’économiser la batterie et au mieux d’en informer l’utilisateur que quelque chose s’est mal passé. Cela s’utilise comme ceci :
bg.DoWork += (sender, args) =>
{
…
}
BackgroundWorker bg = new BackgroundWorker();
bg.WorkerSupportsCancellation = true;
CancellationTokenSource cts = new CancellationTokenSource(); CancellationToken token = cts.Token;
using (token.Register(() => bg.CancelAsync()))
{
bg.RunWorkerAsync();
cts.Cancel();
while (bg.IsBusy);
}
Stockage local :
Une application peut avoir besoin de se connecter régulièrement à un serveur pour obtenir des données. Cependant votre utilisateur ne dispose pas toujours d'une connexion internet. Le mieux est encore de lui afficher les précédentes données que vous pourriez avoir préalablement enregistrées.
Conserver les données sur le téléphone est une marque de respect pour la batterie de votre utilisateur car ainsi, votre application ne fait pas un nombre important de requête pour se mettre à jour.
Il existe avec Xamarin, de très nombreux moyens pour stocker des données dans une application, ils ne sont d’ailleurs pas tous adaptés à tous les usages. Généralement, le stockage de paramètres se fera dans les settings, celui de données volumineuses dans le système de fichier, et celui de données relationnelles dans une base de données. On retrouve plusieurs outils tels que SQLite, Akavache, Realm ou Couchbase Lite. Certains permettent par ailleurs la synchronisation des données avec un serveur.
Gestion des erreurs :
Dans une application mobile, peut-être plus que partout ailleurs, il est important de surveiller les erreurs survenant au sein de l’application. En effet, celle-ci peut planter à de nombreuses reprises durant le développement mais également ensuite lorsqu’elle est mise en production. Il est donc important de pouvoir récupérer des logs d’erreurs pour les corriger le plus rapidement possible. Une application qui plante sur le téléphone d’un utilisateur à un impact direct sur sa position dans le store.
Il existe de nombreux outils de rapport de d'erreurs, d'analyse de comportements utilisateur et de déploiement tel que Mobile Center, Firebase ou Hockey App. Le mieux est toujours de faire en sorte que l’application ne plante pas et de rapporter au maximum les erreurs dans ces outils pour corriger par la suite.
Il faut également noter que dans le cas de développements asynchrones, les applications peuvent planter silencieusement, sans forcément causer un arrêt immédiat de l’application mais peuvent impacter celle-ci dans ses performances. Il faut donc bien penser à relever les erreurs partout où elles apparaissent. Le mieux étant encore de développer un point d’arrêt sur toutes les exceptions de l’application : un clic droit sur la position d’un point d’arrêt pour faire apparaitre une fenêtre et activer l’option qui suit.

Attention cependant, une application développée un peu rapidement sans prendre ceci en compte peut avoir de très nombreuses pauses dûes aux multiples exceptions précedemment silencieuses. Un petit travail initial de correction est donc à prévoir mais améliore la qualité globale de l’application.
FireAndForget :
Lors du développement d’une méthode sur le clic d’un bouton par exemple, les méthodes de base sont souvent synchrones. Il n’est pas possible de les rendre asynchrones. Les méthodes asynchrones que l’on appelle alors sont dîtes “Fire And Forget” car celle-ci sont sans retour. Le code continuera à s’exécuter sans attendre la fin de la méthode asynchrone.
Une bonne pratique est d’encapsuler toute la logique dans une autre méthode qui pourra alors effectuer tout son traitement sans problème :
private void Button_Click(object sender, EventArgs e)
{
MethodAsync();
}
private async Task MethodAsync()
{
await ViewModel.MethodVMAsync();
RandomView.Alpha = 0;
RefreshUI();
}
{
MethodAsync();
}
private async Task MethodAsync()
{
await ViewModel.MethodVMAsync();
RandomView.Alpha = 0;
RefreshUI();
}
Ainsi dans cet exemple, la méthode “RefreshUI” se trouve dans la méthode asynchrone afin de pouvoir s’exécuter avec certitude après le traitement de la méthode asynchrone contenue dans le ViewModel.
Il est également intéressant de savoir qu'une méthode "async void" est proche d'une méthode "async Task" dans le sens où elle ne dispose d’aucun retour possible mais ne peut intercepter aucune exception créant de nombreux risques d'erreurs silencieuses et de crashs sans pile d’appels.
Binding :
Bien que le pattern MVVM soit fortement conseillé dans le développement d’applications Xamarin, il ne faut pas chercher à le faire n’importe comment pour utiliser à tout prix le Binding dans vos applications. Xamarin.Forms supporte très bien son utilisation avec ses vues et sa gestion de contexte. Mais ce n’est pas aussi bien implémenté dans la partie Native du produit.
La plupart des données existent le plus souvent en lecture seule, et ne nécessite pas forcément de se lier avec du Binding qui pourrait alourdir légèrement les performances de l’application. Dans la plupart des cas, un abonnement sur la méthode “OnPropertyChanged” du ViewModel suffit amplement à rafraîchir les données de la vue et pourquoi pas effectuer des traitements supplémentaires. De plus, cela évite les erreurs de Binding pouvant survenir sur des éléments qui ne sont pas encore présent dans la vue.
private void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case nameof(ViewModel.PropertyWithAName):
...
break;
}
}
{
switch (e.PropertyName)
{
case nameof(ViewModel.PropertyWithAName):
...
break;
}
}
Conclusion
Ces quelques astuces peuvent donner à votre application une sensation de fluidité plus importante. Les utilisateurs seront donc plus à même d'apprécier votre travail et de le garder dans leurs smartphones. Cette valeur est d'autant plus importante que les magasins d'applications comptent dans leur algorithme de classement le nombre d'étoiles que votre application peut recevoir mais également le nombre de crashs, le nombre d'installations et de désinstallation ainsi que de nombreuses autres variables. Tâchez de toujours faire en sorte de répondre au plus de critères possibles pour atteindre le top du classement et tenter d'y rester.