Les applications mobiles utilisent de plus en plus de liens profonds. Ce sont des liens qui vous permettent non seulement d'accéder à l'application de l'extérieur, mais également d'accéder à un écran spécifique. Vladislav Kozhushko, un développeur Android de Yandex.Food, a expliqué pourquoi nous avons implémenté la navigation à partir de Jetpack pour implémenter des liens profonds, quels problèmes nous avons rencontrés, comment ils ont été résolus et ce qui s'est finalement produit.
- Bonjour à tous! Je m'appelle Vlad. Je m'intéresse au développement Android depuis 2013, je travaille chez Yandex.Ed depuis l'été dernier. Je vais vous parler de notre façon d'introduire la bibliothèque des composants de navigation dans une application de combat.
Tout a commencé avec le fait que nous avions la tâche technique de refactoriser la navigation. Ensuite, les chefs de produit sont venus vers nous et nous ont dit que nous ferions des liens profonds, il y en aurait beaucoup, ils mèneraient à différents écrans.
Et ici, nous avons pensé - la navigation présentée sur Google I / O 2018 est très bien adaptée à la mise en œuvre de tâches sur les liens profonds. Nous décidons de voir ce qui se passe. Nos collègues avec iOS dans Xcode ont un éditeur graphique pratique dans lequel ils peuvent utiliser la souris pour piquer la disposition entière des écrans, ainsi que définir les transitions entre les écrans. Maintenant, nous avons également une telle opportunité, nous pouvons utiliser la souris pour définir les transitions, les liens profonds vers les écrans et les associer à des fragments. De plus, nous pouvons définir des arguments à l'écran.

Autrement dit, les arguments doivent être bloqués dans un éditeur d'interface utilisateur ou écrits en XML. En plus de basculer entre les écrans, une balise d'action est apparue, dans laquelle nous indiquons son identifiant et l'écran vers lequel nous devons aller.

Nous avons également indiqué le lien profond avec lequel nous voulons ouvrir l'écran. Dans ce cas, il existe un paramètre itemId, si nous transmettons un paramètre de ce type, et qu'il conduira à un fragment, alors la valeur de ce paramètre sera transmise aux arguments de fragment, et nous pouvons l'obtenir et l'utiliser avec la clé itemId. La bibliothèque prend également en charge les liaisons montantes. Si nous définissons la liaison montante, par exemple, navdemo.ru/start/{itemId}, nous n'aurons plus besoin de prendre soin d'enregistrer les schémas http / https pour ouvrir de tels diplinks. La bibliothèque fera tout pour nous.
Parlons maintenant des arguments. Nous avons ajouté deux arguments, entier et booléen, et leur avons également donné des valeurs par défaut.

Après cela, lors de l'assemblage du fragment, nous aurons la classe NextFragmentArgs. Il a un constructeur avec lequel vous pouvez assembler et définir les arguments dont nous avons besoin. Il existe également des getters dans la classe NextFragmentArgs pour obtenir nos arguments. Vous pouvez collecter NextFragmentArgs à partir d'un ensemble et convertir un ensemble, ce qui est très pratique.

À propos des principales fonctionnalités de la bibliothèque. Elle a un éditeur d'interface utilisateur pratique dans lequel nous pouvons faire toute la navigation. Nous obtenons un balisage XML lisible qui se gonfle de la même manière que les vues. Et nous n'avons pas autant de mal que les développeurs iOS quand ils ont corrigé quelque chose dans l'éditeur graphique, et beaucoup de choses ont changé.
Les liens profonds peuvent fonctionner à la fois avec des fragments et avec Activity, et pour cela, vous n'avez pas besoin d'enregistrer une grande quantité d'IntentFilter dans le manifeste. Nous prenons également en charge les liaisons montantes, et avec Android 6, vous pouvez activer la vérification automatique pour vérification. De plus, lors de l'assemblage de projets, la génération de code a lieu avec des arguments à l'écran souhaité. La navigation prend en charge les graphiques imbriqués et la navigation imbriquée, ce qui vous permet de décomposer logiquement toute la navigation en sous-composants distincts.

Nous allons maintenant parler de notre chemin, que nous avons parcouru, en présentant la bibliothèque. Tout a commencé avec la version alpha 3. Nous avons tout implémenté, nous avons remplacé toute la navigation par des composants de navigation, tout est super, tout fonctionne, diplinks ouvert, mais des problèmes sont apparus.

Le premier problème est une exception IllegalArgumentException. Il est apparu dans deux cas: l'incohérence du graphe, car il y avait une désynchronisation de la représentation du graphe et des fragments sur la pile, à cause de cela il y avait une exception. Le deuxième problème est le double clic. Lorsque nous avons fait le premier clic, nous naviguons. Passe à l'écran suivant et l'état du graphique change. Lorsque nous effectuons le deuxième clic, le graphique est déjà dans un nouvel état, et il essaie de faire l'ancienne transition, qui n'existe plus, nous obtenons donc une telle exception. Dans cette version, diplinks ne s'est pas ouvert, dont le schéma contient un point, par exemple, project.company. Cela a été décidé dans les futures versions de la bibliothèque, et dans la version stable, tout fonctionne bien.
De plus, les éléments partagés ne sont pas pris en charge. Vous avez probablement vu comment Google Play fonctionne: il y a une liste d'applications, vous cliquez sur l'application, votre écran s'ouvre et une belle animation de déplacement de l'icône se produit. Nous l'avons également dans l'application sur la liste des restaurants, mais nous avions besoin d'un support pour les éléments partagés. De plus, SafeArgs ne fonctionnait pas pour nous, nous avons donc vécu sans eux.

Il était facile de réparer le lien profond. Il était nécessaire de remplacer le schéma qui était dans la bibliothèque par son propre schéma, qui prend en charge le point. Avec l'aide de la réflexion, nous frappons sur la classe, changeons la valeur de l'expression régulière, et tout fonctionne.

Pour corriger le double clic, nous avons utilisé la méthode suivante. Nous avons des fonctions d'extension pour définir les clics dans la navigation. Après avoir cliqué sur un bouton ou un autre élément, nous mettons à jour le ClickListener et faisons la navigation pour éviter une double transition. Ou si vous avez RxJava dans votre projet, je recommande d'utiliser la bibliothèque RxBindingsgs de Jake Worton, et avec elle, vous pouvez gérer les événements de View dans un style réactif, en utilisant les opérateurs à notre disposition.

Parlons des éléments partagés. Puisqu'ils sont apparus un peu plus tard, nous avons décidé de terminer le navigateur, d'y ajouter la navigation. Nous sommes programmeurs, pourquoi pas?

Le raffinement était le suivant: nous héritons notre navigateur du navigateur, qui se trouve dans la bibliothèque. Tout le code n'est pas présenté ici, mais c'est la partie principale que nous avons finalisée. Je veux noter qu'avant de faire la navigation, l'état du FragmentManager est vérifié. S'il a été enregistré, nous perdons nos équipes. À mon avis, c'est un défaut.
De plus, lorsque nous démarrons une transaction fragmentée, nous créons une transaction et définissons toutes nos vues qui doivent être fouillées. Mais la question est, quel genre de classe est cette TransitionDestination? Il s'agit de notre classe personnalisée dans laquelle il est possible de définir des vues. Nous l'héritons de Destination et étendons la fonctionnalité. Nous définissons des vues et notre destination est prête à les partager.

La partie suivante - nous devons faire de la navigation. Lorsque nous cliquons sur le bouton, nous recherchons la destination de l'id, nous retirons le haut du graphique vers lequel nous devons aller. Après cela, nous le transformerons en TransitionDestination, dans lequel nous avons des vues. Ensuite, nous configurons toutes nos vues pour animer les transitions et faisons la navigation. Tout fonctionne, tout est super. Mais alors alpha06 est apparu.
Cela ne signifie pas que nous avons fait des sauts entre les versions. Nous avons essayé de mettre à jour les bibliothèques au besoin, mais ce sont peut-être les changements les plus fondamentaux que nous avons rencontrés.
Il y avait des problèmes avec alpha06. Comme il s'agissait d'une version alpha de la bibliothèque, il y avait des changements constants associés au changement de nom des méthodes, des rappels, des interfaces, sans parler du fait que des paramètres étaient ajoutés et supprimés dans les méthodes. Depuis que nous avons écrit notre propre navigateur, nous avons dû synchroniser le code du navigateur de la bibliothèque avec le nôtre afin de compléter également les corrections de bugs et les nouvelles fonctionnalités.
De plus, dans la bibliothèque elle-même, au fur et à mesure que nous passons des premières versions alpha aux versions stables, le comportement a changé, certaines fonctionnalités ont été supprimées. Il y avait un indicateur launchDocument, mais il n'a jamais été utilisé, puis il a été supprimé.

Par exemple, il y a eu un tel changement dans lequel les développeurs ont déclaré que la méthode navigUp () qui fonctionne avec DrawerLayout est obsolète, utilisez-en une autre, dans laquelle les paramètres sont simplement échangés.


Notre prochaine grande migration était à alpha11. Ici, les principaux problèmes de navigation ont été résolus lors de l'utilisation du graphique. Nous avons finalement retiré notre contrôleur dopé et utilisé tout ce qui était hors de la boîte. Les arguments sûrs ne fonctionnaient toujours pas pour nous et nous étions bouleversés.
Ensuite, beta01 est sorti, et dans cette version, pratiquement rien n'a changé lorsque la navigation s'est comportée, mais le problème suivant est apparu: si un certain nombre d'écrans sont ouverts dans l'application, alors nous devons vider la pile avant d'ouvrir notre diplink. Ce comportement ne nous convenait pas. Les arguments sûrs ne fonctionnaient toujours pas.

Nous avons écrit un problème sur Google, pour lequel on nous a dit que toutes les règles avaient été conçues à l'origine, et dans le code lui-même, c'était parce qu'avant de passer au lien profond, nous sommes retournés au fragment racine en utilisant l'ID du graphique qui se trouve à la racine. Et également dans la méthode setPopUpTo (), le vrai drapeau est passé, disant que, étant revenu à cet écran, nous devons également le supprimer de la pile.

Nous avons décidé de rendre notre navigateur dopé et de corriger ce que nous pensons être faux.

Voilà, le problème d'origine qui a provoqué l'effacement de la pile. Nous l'avons résolu comme suit. On vérifie si startDestination est égal à zéro, l'écran initial, puis on va l'utiliser, prendre comme identifiant l'identifiant du graphe. Si notre ID startDestination n'est pas nul, nous prendrons cet ID dans le graphique, grâce auquel nous ne pouvons pas effacer la pile et ouvrir le diplink au-dessus du contenu que nous avons. Ou, en option, vous pouvez simplement supprimer la fenêtre contextuelle true dans les options de navigation. En théorie, tout devrait aussi fonctionner.
Et enfin, une version stable sort. Nous étions heureux, nous pensions que tout allait bien, mais dans la version stable, le comportement, dans l'ensemble, n'a pas changé. Ils l'ont juste finalisé. Nous avons finalement obtenu des arguments sûrs, nous avons donc commencé à ajouter activement des arguments sur nos écrans et à les utiliser partout dans le code. Nous avons également constaté que la navigation ne fonctionne pas avec DialogFragments. Puisque nous avions DialogFragments, nous voulions tous les transférer sur un graphique, en XML, et décrire les transitions entre eux. Mais nous n'avons pas réussi.

Double ouverture. Nous avons également eu un problème qui nous hantait depuis la toute première version - la double ouverture d'Activity lors d'un démarrage à froid de l'application.

Cela se passe comme suit. Il existe une merveilleuse méthode de gestion des liens profonds que nous pouvons appeler à partir de notre code, par exemple, lorsque nous sommes en activité, nous obtenons onNewIntent () afin d'intercepter un lien profond. Ici, l'indicateur ACTIVITY_NEW_TASK revient à Intent lorsque l'application est lancée à l'aide du lien profond, ce qui se produit ainsi: une nouvelle activité démarre et s'il y a une activité en cours, elle est supprimée. Par conséquent, si nous lançons l'application, l'écran blanc démarre d'abord, puis il disparaît, un autre écran apparaît et ils sont très beaux.
En conséquence, en introduisant cette bibliothèque, nous avons obtenu les avantages suivants.

Nous avons une documentation pour notre navigation, ainsi qu'une représentation graphique des transitions entre les écrans, et si une personne vient à nous dans un projet, elle le comprend rapidement en regardant le graphique, ouvrant sa présentation dans le Studio.
Nous avons SingleActivity. Tous les écrans sont faits sur des fragments, tous les diplinks mènent à des fragments, et je pense que c'est pratique.
Le résultat a été une simple liaison du lien profond avec des fragments, il suffit d'ajouter la balise deeplink au fragment, et la bibliothèque fait tout pour nous. Nous avons également divisé notre navigation en sous-graphiques imbriqués, nous avons fait de la navigation imbriquée. Ce sont des choses différentes. Juste un graphique imbriqué, en fait, il est inclus dans le graphique, et la navigation imbriquée est lorsqu'un navigateur distinct est utilisé pour parcourir les écrans.
Nous changeons également le graphique dynamiquement dans le code, nous pouvons ajouter des sommets, nous pouvons supprimer des sommets, nous pouvons changer l'écran de démarrage - tout cela fonctionne.
Nous avons presque oublié comment travailler avec FragmentManager, car toute la logique de travailler avec lui est encapsulée dans la bibliothèque, et la bibliothèque fait toute la magie pour nous. La bibliothèque fonctionne également avec DrawerLayout, si vous avez un ensemble de fragments racine, la bibliothèque elle-même dessinera un hamburger dessus, et lors du passage aux écrans suivants, elle dessinera une flèche et le fera de manière animée lors du retour de l'avant-dernier fragment.
Nous avons également transféré tous les arguments, la plupart d'entre eux, vers SafeArgs, et tout est créé lorsque nous construisons le projet. De plus, nous avons fait face à tous les problèmes qui nous tourmentaient et avons modifié la bibliothèque pour l'adapter à nos besoins.
SafeArgs peut générer du code Kotlin, un plugin séparé est utilisé pour cela.

De plus, la bibliothèque présente des inconvénients. Le premier est que le nettoyage de la pile a été livré à la version stable. Je ne sais pas pourquoi cela a été fait, c'est peut-être si pratique pour quelqu'un, mais dans le cas de notre application, nous aimerions ouvrir des liens profonds en plus du contenu que nous avons.
Les fragments eux-mêmes sont créés par le fragment de navigateur et sont créés par réflexion. Je ne pense pas que ce soit un plus dans la mise en œuvre. La condition de lien profond n'est pas prise en charge. Votre application peut avoir des écrans secrets qui ne sont disponibles que pour les utilisateurs autorisés. Et pour ouvrir les liens profonds par condition, vous devez écrire des béquilles pour cela, car vous ne pouvez pas définir un processeur de liens profonds dans lequel nous prendrions le contrôle et dirions quoi faire, soit ouvrir l'écran par le lien profond ou ouvrir un autre écran.
De plus, les commandes de transition sont perdues, tout cela parce que le fragment du navigateur vérifie l'état s'il est enregistré ou non, et s'il est enregistré, alors nous ne faisons rien.
Nous avons également dû modifier la bibliothèque avec un fichier, ce n'est pas un plus. Et un autre inconvénient important - nous n'avons pas la possibilité d'ouvrir une chaîne d'écrans devant le lien profond. Dans le cas de notre application, nous souhaitons faire un lien profond vers le panier, avant d'ouvrir tous les écrans précédents: restaurants, un restaurant spécifique, et seulement après cela le panier.
De plus, pour travailler avec la navigation, vous devez avoir une instance View. Pour obtenir un navigateur, vous devez vous tourner vers la vue - la bibliothèque de navigation elle-même contactera les parents de cette vue - et essayer d'y trouver le navigateur. Si elle le trouve, l'écran passe à l'écran dont nous avons besoin.
Mais la question se pose: vaut-il la peine d'utiliser la bibliothèque au combat? Je dirai oui si:

Si nous devons obtenir un résultat rapide. La bibliothèque est implémentée très rapidement, elle peut être insérée dans le projet en 20 minutes environ, toutes les transitions se piquent avec la souris, tout cela est pratique. Il est également écrit rapidement en XML, rapidement assemblé et fonctionne rapidement. Tout est super.
Si vous avez besoin de plusieurs écrans dans l'application qui fonctionnent avec des liens profonds, et qu'il n'y a aucune condition dans laquelle ils doivent s'ouvrir.
Pas d'activité unique. Ici, je veux dire non seulement une seule activité. Nous pouvons faire de la navigation à la fois sur des fragments avec une seule activité, et sur un mélange de fragments et d'activité, juste dans le graphique, vous pouvez également décrire l'activité. Pour cela, un fournisseur est utilisé à l'intérieur du navigateur, dans lequel il existe deux implémentations de navigation. L'une sur l'activité, qui crée des intentions pour accéder aux activités souhaitées, la deuxième implémentation est un navigateur de fragments qui fonctionne en interne avec un gestionnaire de fragments.
Si vous n'avez pas de logique complexe pour basculer entre les écrans. Chaque application est unique à sa manière, et s'il existe une logique complexe pour ouvrir les écrans, cette bibliothèque n'est pas pour vous.
Vous êtes prêt à aller dans la source et à la modifier comme nous le faisons. C'est en fait très intéressant.

Je dirai non si l'application a une logique de navigation compliquée, si vous devez ouvrir une chaîne d'écrans avant le lien profond, si vous avez besoin de conditions difficiles pour ouvrir les écrans via le lien profond. En principe, avec l'exemple d'autorisation, cela peut être fait, l'écran dont vous avez besoin s'ouvre simplement, et en plus de cela est l'écran d'autorisation. Si l'utilisateur ne se connecte pas, vous êtes transféré sur la pile vers l'écran précédent, avant lequel l'écran secret a été ouvert. Une telle décision béquille.
La perte d'équipes est essentielle pour vous. Le même Cicerone peut enregistrer des commandes dans le tampon, et lorsque le navigateur devient disponible, il les exécute.
Si vous ne souhaitez pas terminer le code, cela ne signifie pas que vous, en tant que développeur, ne voulez pas faire quelque chose. Supposons que vous ayez un délai, les chefs de produit vous disent que vous devez supprimer des fonctionnalités, une entreprise veut déployer des fonctionnalités. Vous avez besoin d'une solution clé en main qui fonctionne immédiatement. Les composants de navigation ne concernent pas ce cas.
Encore une chose critique - DialogFragments ne sont pas pris en charge. Un navigateur qui fonctionnerait avec eux pourrait être ajouté, mais pour une raison quelconque, il n'a pas été ajouté. Le problème est qu'ils s'ouvrent eux-mêmes comme des fragments ordinaires. La case à cocher pour isShowing n'est pas définie et, par conséquent, le cycle de vie DialogFragments pour la création d'une boîte de dialogue n'est pas exécuté. En fait, DialogFragment s'ouvre comme un fragment normal, tout l'écran sans créer de fenêtre.
C’est tout pour moi. Creusez la source, c'est vraiment intéressant et excitant. Voici
des documents utiles pour ceux qui souhaitent travailler avec la bibliothèque de navigation. Merci de votre attention.