La combinaison d'une approche multiplateforme et native dans le développement d'applications mobiles

Pour publier des applications pour une seule plate-forme mobile n'est pas pertinent et vous devez prendre soin de développer deux versions à la fois, pour iOS et Android. Et ici, vous pouvez choisir deux façons: travailler dans les langages de programmation "natifs" pour chaque système d'exploitation ou utiliser des frameworks multiplateformes.

Lors du développement d'un des projets chez DD Planet, je me suis appuyé sur la dernière option. Et dans cet article, je parlerai de l'expérience de développement d'une application multiplateforme, des problèmes que nous avons rencontrés et des solutions trouvées.

Trois approches pour développer des applications mobiles multiplateformes


Pour commencer, réfléchissez aux approches utilisées lorsque vous devez obtenir deux applications à la fois: pour iOS et Android.

Le premier est le plus cher, en temps et en ressources: développer une application distincte pour chaque plateforme. La complexité de cette approche réside dans le fait que chacun des systèmes d'exploitation nécessite sa propre approche: cela s'exprime à la fois dans le langage dans lequel le développement est en cours (pour Android - Java ou Kotlin, pour iOS - Objective-C ou Swift), et les méthodes de description de la partie UI applications (fichiers axml et xib ou storyboard, respectivement).

Ce seul fait nous amène au fait que pour cette approche, il est nécessaire de former deux équipes de développement. De plus, vous devrez dupliquer la logique pour chacune des plateformes: interaction avec l'API et la logique métier.

image

Mais que se passe-t-il si le nombre d'API utilisées augmente?

image

Cela soulève la question: comment réduire la quantité requise de ressources humaines? Débarrassez-vous de la nécessité de dupliquer le code pour chaque plate-forme. Il existe un nombre suffisant de cadres et de technologies qui résolvent ce problème.

L'utilisation d'un framework multiplateforme (Xamarin.Forms, par exemple) permet d'écrire du code dans un langage de programmation et de décrire une fois la logique des données et la logique de l'interface utilisateur, en un seul endroit. Par conséquent, la nécessité d'utiliser deux équipes de développement disparaît. Et à la suite de la compilation du projet, nous obtenons deux applications natives à la sortie. Et c'est la deuxième approche.

image

Beaucoup, je pense, savent ce qu'est Xamarin, ou du moins en ont entendu parler, mais comment ça marche? Xamarin est basé sur l'implémentation open source de la plate-forme .NET - Mono. Mono comprend son propre compilateur C #, son runtime, ainsi qu'un certain nombre de bibliothèques, y compris l'implémentation de WinForms et ASP.Net.

L'objectif du projet est de permettre aux programmes écrits en C # de s'exécuter sur des systèmes d'exploitation autres que Windows - systèmes Unix, Mac OS et autres. Le framework Xamarin lui-même, en substance, est une bibliothèque de classes qui fournit aux développeurs un accès au SDK et aux compilateurs de la plateforme pour ceux-ci. Xamarin.Forms, à son tour, vous permet non seulement d'écrire pour les deux plates-formes dans une seule langue, mais également de concevoir des écrans à l'aide du balisage XAML, familier à ceux qui avaient déjà une expérience avec les applications WPF. Grâce à l'assemblage du projet, nous obtenons un aspect presque identique sur toutes les plates-formes, car au stade de la compilation, tous les contrôles XF sont convertis en natif pour chaque plate-forme.

image

Le développeur est obligé d'écrire du code pour chaque plate-forme uniquement si l'accès à des fonctionnalités de la plate-forme est nécessaire (par exemple, un scanner d'empreintes digitales ou le niveau de la batterie) ou s'il est nécessaire d'affiner le comportement de contrôle. Dans certains cas, lors du développement d'une application, il peut être nécessaire d'écrire du code dépendant de la plate-forme, mais même dans ce cas, personne n'interdit de prendre des fonctions de plate-forme à l'interface et d'interagir avec elle à partir d'un projet commun à l'avenir.

Un langage de programmation, peu de code, etc. Tout cela semble beau mais, Xamarin.Forms n'est pas une solution miracle, et toute sa beauté se transforme en pierres de réalité. Dès qu'une situation survient lorsque les commandes XF intégrées ne répondent plus à leurs exigences, la structure des écrans et des commandes devient de plus en plus compliquée. Pour assurer un travail confortable avec les écrans d'un projet commun, vous devez écrire de plus en plus de rendus personnalisés.

Cela passera à la troisième approche, que nous utilisons lors du développement d'applications.

Nous avons déjà découvert que l'utilisation de Xamarin Forms peut compliquer le travail plutôt que le simplifier. Par conséquent, pour mettre en œuvre des écrans d'une architecture complexe, des éléments de conception et des contrôles fondamentalement différents des natifs, un compromis et la possibilité de combiner les première et deuxième approches ont été trouvés.

Nous avons tous les mêmes trois projets: un projet PCL commun, mais sans Xamarin Forms, et deux projets Xamarin Android et Xamarin iOS. Il y a toujours la possibilité d'écrire tout dans une seule langue, une logique commune entre deux projets, mais il n'y a pas de limitations d'un seul balisage XAML. Le composant UI est contrôlé par chaque plate-forme et utilise des outils natifs, sur Android - natif AXML, sur des fichiers iOS - XIB. Chaque plate-forme a la capacité de se conformer à ses directives, car la connexion entre le Core et les projets de plate-forme n'est organisée qu'au niveau des données.

Pour organiser une telle relation, vous pouvez utiliser le modèle de conception MVVM et son implémentation assez populaire pour Xamarin - MVVMCross. Son utilisation vous permet de conserver un ViewModel commun à chaque écran, qui décrit toute la «logique métier» de l'œuvre, et de confier son rendu à la plateforme. Il permet également à deux développeurs de travailler avec le même écran (l'un avec la logique - l'autre avec l'interface utilisateur) et de ne pas interférer l'un avec l'autre. En plus de la mise en œuvre du modèle, nous obtenons un nombre suffisant d'outils pour le travail: la mise en œuvre de DI et IoC. Pour élever l'interaction avec la plateforme au niveau du code commun, un développeur a juste besoin de déclarer une interface et de l'implémenter sur la plateforme. Pour des choses typiques, MvvmCross fournit déjà un ensemble de ses propres plugins. Dans l'équipe, nous utilisons le plugin messenger pour échanger des messages entre la plateforme et le code commun et le plugin pour travailler avec des fichiers (sélection d'images dans la galerie, etc.).

Nous résolvons les problèmes de conception complexe et de navigation multi-niveaux


Comme mentionné précédemment, lors de l'utilisation de représentations complexes à l'écran, le cadre peut compliquer davantage la vie que la rendre plus facile. Mais qu'est-ce qu'on appelle un élément complexe? Étant donné que je suis principalement engagé dans le développement iOS, un exemple de cette plate-forme sera considéré. Par exemple, une chose aussi banale qu'un champ d'entrée peut avoir plusieurs états et suffisamment de logique pour la commutation et la visualisation.

image

Au cours de l'utilisation des entrées utilisateur, un tel contrôle d'entrée a été développé ici. Il peut élever son nom au-dessus du champ de saisie, travailler avec des masques, définir des préfixes, des postfixes, notifier lorsque CapsLock est enfoncé, valider les informations en deux modes: interdiction d'entrée et de sortie des informations d'erreur. La logique à l'intérieur du contrôle prend environ ~ 1000 lignes. Et, il semblerait: qu'est-ce qui peut être compliqué dans la conception du champ de saisie?

Un exemple simple d'un contrôle complexe que nous avons vu. Et les écrans?

image

Pour commencer, je précise que dans la plupart des cas, un écran d'application est une classe - UIViewController, décrivant son comportement. Pendant le développement, la création d'une navigation à plusieurs niveaux était nécessaire. Le concept de l'application développée se résume à gérer votre bien immobilier et à interagir avec les voisins et les organisations municipales. Par conséquent, trois niveaux de navigation ont été construits: propriété, niveau de présentation (domicile, ville, région) et type de contenu. Toute commutation s'effectue sur un seul écran.

Cela a été fait pour que l'utilisateur, où qu'il soit, comprenne quel type de contenu il voit. Pour organiser une telle navigation, l'écran principal de l'application ne se compose pas d'un seul contrôleur. Visuellement, il peut être divisé en 3 parties, mais peut-on essayer de deviner combien de contrôleurs sont utilisés ici?

image

Quinze contrôleurs principaux. Et c'est juste pour le contenu.

Ici, un tel monstre vit sur l'écran principal et se sent plutôt bien. Quinze contrôleurs pour un écran, c'est bien sûr beaucoup. Cela affecte la vitesse de l'application entière et vous devez l'optimiser d'une manière ou d'une autre.

Nous avons refusé l'initialisation synchrone: tous les modèles de vue sont initialisés en arrière-plan et uniquement lorsque cela est nécessaire. Pour réduire le temps de rendu, nous avons également abandonné les fichiers xib pour ces écrans: le positionnement absolu et les mathématiques sont toujours plus rapides que le calcul des dépendances entre les éléments.

Pour garder une trace de tant de contrôleurs, vous devez comprendre:

  • Dans quel état est chacun d'eux;
  • Où est l'utilisateur;
  • Ce qu'il attend de voir lors du passage à un autre contrôleur.

Pour ce faire, j'ai écrit un processeur de navigation distinct qui stocke des informations sur l'emplacement de l'utilisateur, le type de contenu qu'il consulte, l'historique de navigation, etc. Il contrôle l'ordre et la nécessité de l'initialisation.

Étant donné que chaque onglet est un curseur de contrôleur (afin de créer une transition de balayage sur eux), vous devez comprendre: chacun d'eux peut être dans son propre état (par exemple, "News" est ouvert sur l'un et "Voting" sur l'autre). Ceci est suivi par le même processeur de navigation. Même en changeant le niveau de présentation du domicile à la région, nous resterons sur le même type de contenu.

Nous contrôlons le flux de données en temps réel


En travaillant avec autant de données dans l'application, vous devez organiser la livraison des informations pertinentes dans toutes les sections en temps réel. Pour résoudre ce problème, 3 méthodes peuvent être distinguées:

  1. Accéder à l'API par des minuteries ou des déclencheurs et demander à nouveau le contenu pertinent sur les écrans;
  2. Avoir une connexion permanente au serveur et recevoir les changements en temps réel;
  3. Recevez push avec des changements de contenu.

Chaque approche a ses avantages et ses inconvénients, il est donc préférable d'utiliser les trois, en ne choisissant que les points forts de chacune. Nous avons divisé sous condition le contenu de l'application en plusieurs types: chaud, régulier et service. Ceci est fait afin de déterminer le temps acceptable entre l'événement et la notification de l'utilisateur. Par exemple, nous voulons voir un message de discussion immédiatement après qu'il nous l'ait envoyé - il s'agit d'un contenu brûlant. Autre option: sondage auprès des voisins. Cela ne fait aucune différence lorsque nous le voyons, maintenant ou dans une minute, car c'est un contenu ordinaire. Les petites notifications à l'intérieur de l'application (messages non lus, commandes, etc.) sont un contenu de service qui nécessite une livraison urgente, mais ne prend pas beaucoup de données.

Il s'avère:

  • Contenu chaud - connexion permanente avec l'API;
  • Contenu normal - requêtes http à l'API;
  • Contenu du système - notifications push.

La chose la plus intéressante est de maintenir une connexion constante. Écrire votre propre client pour travailler avec des sockets Web est une étape dans le lapin, vous devez donc chercher d'autres solutions. En conséquence, nous nous sommes arrêtés à la bibliothèque SignalR. Voyons voir ce que c'est.

ASP.Net SignalR est une bibliothèque de Microsoft qui simplifie l'interaction client-serveur en temps réel, fournissant une communication bidirectionnelle entre le client et le serveur. Le serveur comprend une API à part entière pour gérer la connexion, les événements de déconnexion de connexion, un mécanisme pour combiner les clients connectés en groupes et l'autorisation.

SignalR peut utiliser des requêtes Websockets, LongPolling et http comme transport. Vous pouvez spécifier le type de transport de force ou faire confiance à la bibliothèque: si websocket peut être utilisé, il fonctionnera via websocket, si ce n'est pas possible, il descendra jusqu'à ce qu'il trouve un transport acceptable. Ce fait s'est avéré très pratique, étant donné qu'il est prévu de l'utiliser sur des appareils mobiles.

Total, quel avantage en tirons-nous:

  • Possibilité d'échanger des messages de tout type entre le client et le serveur;
  • Le mécanisme pour basculer automatiquement entre les sockets Web, les requêtes de mise en pool longue et les requêtes Http;
  • Informations sur l'état actuel de la connexion;
  • Une opportunité d'unir les clients en groupes;
  • Méthodes pratiques pour manipuler la logique d'envoi de messages dans un groupe;
  • La possibilité de faire évoluer le serveur.

Bien sûr, cela ne satisfait pas tous les besoins, mais facilite sensiblement la vie.

A l'intérieur du projet, un wrapper est utilisé sur la bibliothèque SignalR, ce qui simplifie encore plus son travail, à savoir:

  • Surveille l'état de la connexion, se reconnecte selon les conditions spécifiées et en cas de rupture;
  • Capable de remplacer ou de rouvrir rapidement la connexion, tuant de manière asynchrone l'ancienne et la remettant au garbage collector pour la déchirer - comme il s'est avéré, la méthode de connexion fonctionne dix fois plus rapidement que la méthode de fermeture (Dispose ou Stop), et c'est la seule façon de la fermer;
  • Organise une file d'attente pour l'envoi de messages afin que la reconnexion ou la réouverture de la connexion n'interrompe pas l'envoi;
  • Transfère le contrôle aux délégués appropriés en cas d'erreurs imprévues.

Chacun de ces wrappers (nous les appelons clients) fonctionne en tandem avec le système de mise en cache et, en cas de déconnexion, ne peut demander que les données qu'il aurait pu manquer pendant cette période. «Chacun» car plusieurs composés actifs sont détenus simultanément. À l'intérieur de l'application, il y a un messager à part entière et un client distinct est utilisé pour le réparer.

Le deuxième client est responsable de la réception des notifications. Comme je l'ai déjà dit, le type de contenu habituel est obtenu via les requêtes http, à l'avenir sa mise à jour incombera à ce client, qui en rend compte tous les changements importants (par exemple, le vote est passé d'un statut à un autre, la publication de nouvelles).

Visualisez les données dans l'application


image

Obtenir des données est une chose, montrer en est une autre. La mise à jour des données en temps réel a ses propres difficultés. Au minimum, vous devez décider comment présenter ces mises à jour à l'utilisateur. Dans l'application, nous utilisons trois types de notifications:

  1. Notification de contenu non lu;
  2. Mise à jour automatique des données sur l'écran;
  3. Offre de contenu.

La façon la plus familière et la plus ordinaire de montrer qu'il y a du nouveau contenu est de mettre en surbrillance l'icône de la section. Ainsi, presque toutes les icônes ont la possibilité d'afficher le notificateur de contenu non lu sous la forme d'un point rouge. Les choses les plus intéressantes sont les mises à jour automatiques.

La mise à jour automatique des données n'est possible que lorsque le nouveau contenu ne réorganise pas l'écran et ne modifie pas la taille des contrôles. Par exemple, sur l'écran d'enquête: les informations sur les votes ne changeront que la valeur de la barre de progression et les pourcentages. De tels changements n'entraîneront aucun redimensionnement; ils peuvent être appliqués instantanément sans problème.

Des difficultés surviennent lorsque vous devez ajouter un nouveau contenu à des listes. En fait, toutes les listes de l'application sont ScrollView et ont plusieurs caractéristiques: taille de la fenêtre, taille du contenu et position du défilement. Tous ont un début statique (haut de l'écran avec les coordonnées 0; 0) et peuvent s'étendre vers le bas. Ajouter du nouveau contenu dans la liste, à la fin, ne pose aucun problème, la liste durera. Mais le nouveau contenu devrait apparaître en haut, et voici l'image:

image

Étant sur 3 éléments, nous serons sur 2 - le parchemin rebondira. Et comme le nouveau contenu peut arriver en permanence, l'utilisateur ne pourra pas faire défiler normalement. Vous pourriez dire: pourquoi ne pas calculer la taille du nouveau contenu et déplacer le défilement vers le bas sur cette valeur? Oui, cela peut être fait. Mais alors vous devez contrôler manuellement la position de défilement, et si à ce moment l'utilisateur défile dans n'importe quelle direction, son action sera interrompue. C'est pourquoi ces écrans ne peuvent pas être mis à jour en temps réel sans le consentement de l'utilisateur.

La meilleure solution dans cette situation serait d'informer l'utilisateur que pendant qu'il fait défiler le flux, quelqu'un a publié du nouveau contenu. Dans notre conception, cela ressemble à un cercle rouge dans le coin de l'écran. En cliquant dessus, l'utilisateur donne son consentement conditionnel à ce que nous le renvoyions en haut de l'écran et que nous montrions un nouveau contenu.

Avec cette approche, nous avons bien sûr évité les problèmes de «glissement» du contenu, mais ils devaient encore être résolus. À savoir, sur l'écran de discussion, car pendant la communication et l'interaction avec l'écran, le nouveau contenu doit être affiché à différents endroits.

La différence entre le chat et les listes régulières est que le nouveau contenu est en bas de l'écran. Puisqu'il s'agit d'une «queue», vous pouvez y ajouter du contenu sans trop de difficulté. L'utilisateur y passe 90% du temps, ce qui signifie que vous devez constamment garder la position de défilement et la déplacer vers le bas lors de la réception et de l'envoi de messages. Dans une conversation en direct, de telles actions doivent être effectuées assez souvent.

Le deuxième point: charger l'historique tout en faisant défiler vers le haut. Juste au moment de charger l'histoire, nous nous trouvons dans une situation où il est nécessaire de placer des messages au-dessus du niveau de révision (ce qui entraînera des biais) afin que le défilement soit fluide et continu. Et comme nous le savons déjà, afin de ne pas déranger l'utilisateur, il est impossible de contrôler manuellement la position de défilement.

Et nous avons trouvé une solution: nous l'avons retournée. Le retournement d'écran a résolu deux problèmes à la fois:

  1. La queue de la liste est en haut, afin que nous puissions facilement ajouter une histoire sans interférer avec le défilement de l'utilisateur;
  2. Le dernier message est toujours en haut de la liste et nous n'avons pas besoin de faire défiler l'écran avant.

image

Cette solution a également contribué à accélérer le rendu, en éliminant les opérations inutiles avec le contrôle de défilement.

En parlant de performance. Dans les premières versions de l'écran, des baisses notables ont été détectées lors du défilement des messages.Étant donné que le contenu de la «monnaie» est hétéroclite - texte, fichiers, photos - vous devez constamment recalculer la taille de la cellule, ajouter et supprimer des éléments dans la monnaie. Par conséquent, l'optimisation de la bulle était nécessaire. Nous avons fait la même chose qu'avec l'écran principal, en rendant partiellement la pâte avec un positionnement absolu.

Lorsque vous travaillez avec des listes dans iOS, avant de dessiner une cellule, vous devez connaître sa hauteur. Par conséquent, avant d'ajouter un nouveau message à la liste, vous devez préparer toutes les informations nécessaires pour les afficher dans un flux séparé, calculer la hauteur des cellules, traiter les données utilisateur, et seulement après avoir découvert et mis en cache tout ce qui est nécessaire, ajoutez la cellule à la liste.

En conséquence, nous obtenons un défilement fluide et un flux d'interface utilisateur non surchargé.

Pour résumer:


  • Le développement multiplateforme permet d'économiser du temps et de l'argent;
  • , , ;
  • , ;
  • ;
  • SignalR – - ;
  • ;
  • , , ;
  • , SignalR-, , , , .

Source: https://habr.com/ru/post/fr430892/


All Articles