La belle histoire des développeurs Android, de multiples activités et de l’éléphant enchaîné
Nous sommes souvent tenus de faire certaines choses par habitude, en fonction de ce que nous avons appris ou au fil du temps. Une routine au jour le jour, nous pourrions compter sur le même schéma encore et encore, chaque jour, parce que c’est ce que nous avons fait hier. Il y a de fortes chances que nous fassions la même chose demain.
Mais pourquoi?
Parfois, certaines de nos habitudes ne sont pas particulièrement utiles ni efficaces. Peut-être que vous passez encore du temps sur un jeu malgré la «fin de service» à venir en juin. Peut-être que vous cliquez toujours sur / r / androiddev par habitude au lieu de.
Vous utilisez peut-être encore plusieurs activités pour modéliser plusieurs écrans dans votre application Android.
Comme indiqué à l’origine dans le maintenant disparu de Google+,
L’activité est l’entrée dans une application pour interagir avec l’utilisateur. Du point de vue du système, les principales interactions qu’il fournit avec l’application sont les suivantes:
• Gardez une trace de ce que l’utilisateur se soucie actuellement (ce qui est à l’écran) pour assurer le processus d’hébergement qui est en cours d’exécution.
• Sachez que les processus précédemment utilisés contiennent des éléments sur lesquels l’utilisateur peut revenir (activités arrêtées), et donc privilégiez davantage le maintien de ces processus.
• Aidez l’application à gérer la situation dans laquelle son processus est interrompu afin que l’utilisateur puisse reprendre ses activités avec son état antérieur restauré [to the state they had before they were stopped].
Une activité est un composant du système d’exploitation de niveau supérieur qui sert de point d’entrée dans une application Android, lancé par le système d’exploitation en réponse à un type donné d ‘«intention» (qui peut ou non contenir des données), et est associé à une fenêtre afin que nous puissions afficher une mise en page dans son cadre de contenu.
Même. Vous avez appris que c’est la façon de faire les choses sur Android.
C’est définitivement une façon particulière de faire les choses. Mais est-ce vraiment le meilleur façon?
Si vous regardez autour de vous et voyez ce que les développeurs pensent des «applications à activité unique», vous pouvez trouver quelque chose dans le sens de ceci:
«J’aime toujours utiliser deux activités. Un pour la connexion et l’authentification au démarrage, puis un pour l’application réelle. «
Certainement une amélioration par rapport à « une activité par écran », surtout si vous souhaitez éviter les complexités qui découlent de concepts tels que « la réparation des tâches » ou « indicateurs d’intention », et préférez plutôt avoir plus de contrôle sur le comportement de votre propre application.
UNEAprès tout, on pourrait faire valoir que vous tirez toujours le meilleur parti d’une application à activité unique, si vous pouvez vous assurer qu’il n’y a qu’une seule activité sur la pile des tâches en même temps.
Mais qu’est-ce qui rend le flux d’authentification si spécial qu’il aurait besoin de son propre point d’entrée au niveau du système d’exploitation? Peut-il être démarré par d’autres applications directement via une intention spéciale? S’agit-il d’un flux entièrement distinct exposé à d’autres applications (comme ACTION_SHARE, ou prendre une photo avec un appareil photo), indépendant du flux régulier de l’application? Est-il censé être un écran indépendant qui peut se restituer même dans PIP? Le système d’exploitation doit-il veiller à ce que ce nouveau point d’entrée existe?
Sinon (et la réponse est «non»), il ne peut y avoir qu’une seule raison: habitude.
Pour modéliser une application différemment, nous devons abandonner les anciennes chaînes qui nous accrochent, les tutoriels et les pratiques même quand.
Comme également indiqué dans les documents originaux:
Une fois que nous sommes entrés dans ce point d’entrée de votre interface utilisateur, nous ne nous soucions vraiment pas de la façon dont vous organisez le flux à l’intérieur. Faites-en une seule activité avec des modifications manuelles de ses vues, utilisez des fragments (un cadre de commodité que nous fournissons) ou un autre cadre, ou divisez-le en activités internes supplémentaires. Ou faites les trois au besoin. Tant que vous suivez le contact d’activité de haut niveau (il se lance dans l’état approprié et enregistre / restaure dans l’état actuel), cela n’a pas d’importance pour le système.
Qui dit système peu importe comment vous organisez vos flux. Mais vous le faites probablement!
L’article présente quelques options:
- Activité unique avec vues
- Activité unique avec fragments
- Activité unique avec un autre framework (conducteur, RIB, Shards, etc.)
- Plusieurs activités si vraiment nécessaires
Il y avait eu des tentatives de faire en particulier par Square (plaidant pour l’utilisation de Flow et Mortar, certaines bibliothèques brillantes de l’époque, mais qui ne sont plus maintenues ni populaires dans leur forme originale).
Dans Droidcon NYC 2017, Jake Wharton :
« Une activité pour toute l’application. Vous pouvez utiliser des Fragments, mais n’utilisez pas le backstack de Fragments, car c’est mauvais. » – Jake Wharton
En mai 2018, le composant de navigation Jetpack a été annoncé, et avec cela, celui de Google.
Aujourd’hui, nous introduisons le composant Navigation comme cadre pour structurer votre interface utilisateur dans l’application, en mettant l’accent sur la création une application à activité unique le architecture préférée.
Et les fragments ont eu des problèmes de longue date corrigés, y compris des animations de type «glisser au-dessus d’autres fragments» qui sont désormais gérées par le.
Le composant de navigation propose de masquer toutes les transactions de fragments que vous devez écrire manuellement et simplifie l’accès à un NavController à partir de n’importe quelle vue.
Pourtant, les gens se demandent encore «s’ils devraient faire le grand saut». Alors quel est le problème?
La navigation dans les activités est extrêmement intrusive. C’est le « plus facile à ajouter », mais c’est le plus difficile à supprimer et à remplacer. Une fois que tu as startActivity(intent)
ou surtout startActivityForResult(intent, 0)
dans votre code, il est vraiment difficile de l’extraire.
Dans les applications multi-activités, les fragments communiquent par le biais de leur activité, généralement directement le «type d’activité» qui peut les héberger. L’activité existe en tant que superscope partagée pour les fragments, et ils sont tous couplés. Et si nous voulions réutiliser les Fragments, sans leur hôte? Ils dépendent d’une Activité pour gérer leur communication!
En optant pour l’utilisation de plusieurs activités, nous optons pour une architecture rigide difficile à modifier. Disons que j’ai besoin de ces fragments dans un ViewPager avec des onglets. Maintenant, je dois déplacer toute la logique de l’activité vers un fragment, déplacer tous ces fragments pour qu’ils soient des fragments enfants et faire de l’hôte d’activité le fragment parent à la place (remplacez tout appel à activity
avec parentFragment
). Il y a beaucoup de pièces mobiles, il est facile de casser des choses. Imaginez faire cela pour chaque relation activité-fragment. C’est plus facile de ne pas le faire.
Et si on ne se mettait pas dans ce pétrin en premier lieu?
Il existe en fait plusieurs approches pour créer des étendues partagées entre les écrans et simplifier la navigation. Les étendues partagées peuvent servir de «sous-étendue» entre les écrans, sans dépendre de l’activité elle-même pour partager des données ou des événements entre eux.
Les vues sont intrinsèquement imbriquables (mais ne sont pas facilement compatibles avec le cycle de vie), les fragments peuvent être imbriqués et Jetpack Compose prendra en charge l’imbrication des étendues à travers des ambiants, mais les cadres existants comme (par Uber, puis Badoo) ou la bibliothèque de navigation que je gère () ou (2.2.0+) peuvent créer des étendues partagées explicites.
Simple-Stack fournit des «services de portée», tandis que Jetpack Navigation prend en charge les «ViewModels de portée NavGraph» (en utilisant le NavBackStackEntry
d’un ViewModelStoreOwner
et SavedStateRegistryOwner
d'un ViewModel
avec un SavedStateHandle
).
Une fois que vous avez partagé les étendues, les rappels de résultats sont assez simples. Mettez simplement un résultat en attente dans votre portée partagée et lisez-le sur l'écran précédent lorsque vous revenez en arrière (BehaviorRelay, MutableLiveData, EventEmitter, LinkedListChannel, etc.).
Pas besoin de codes de résultat, de bundles ou de communication inter-processus (IPC), car l'étendue partagée est garantie d'exister même lors de la navigation arrière.
Et une fois que vous avez l'un de ces cadres en place, la navigation doit être suffisamment abstraite pour que tout ce dont vous avez besoin soit soit backstack.goTo(SomeScreen())
, ou navController.navigate(SomeGraphDirections.someScreen())
.
N'est-ce pas plus facile que FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK
, ou FLAG_ACTIVITY_BROUGHT_TO_FRONT | FLAG_ACTIVITY_SINGLE_TOP | FLAG_ACTIVITY_REORDER_TO_FRONT
?
Il existe en fait quelques cas d'utilisation parfaitement valides pour l'utilisation de plusieurs activités, où vous n'avez pas vraiment d'autres options.
Multi-fenêtres utilisant les deux panneaux dans un scénario d'écran partagé ou applications multi-processus.
Cela peut également avoir un sens pour les flux indépendants de l'application principale et exposés uniquement pour d'autres applications, telles que ACTION_SHARE
, ou pour le prochain.
Mais vous n'avez généralement pas besoin d'eux pour afficher un deuxième écran.
J'ai mis en place deux exemples assez simples qui montrent toujours l'idée générale, tous deux basés sur le composant de navigation Jetpack d'origine (qui, étonnamment, ne fait aucune mention popUpTo
ou popUpToInclusive="true"
, les deux propriétés qui vous permettent de faire une navigation conditionnelle).
Néanmoins, la «première expérience utilisateur» est un exemple facile à comprendre pour savoir comment NE PAS utiliser une deuxième activité juste pour afficher un écran de connexion.
Voici les exemples:
(Il y a aussi le cadre de navigation appelé qui promet d'abstraire complètement Android, mais malheureusement, je ne l'ai pas pris pour un tour. Il vaut quand même la peine d'y jeter un coup d'œil.)
J'espère que cet article donne un aperçu de la façon dont l'utilisation d'une seule activité peut dans la plupart des cas facilement remplacer la «commodité» qu'offre une approche multi-activités (c'est-à-dire «une portée plus simple» et une «navigation à friction plus faible»), sans nous forcer à un modèle particulier qui se traduit plus tard par une navigation plus délicate entre les écrans, et par ailleurs des transitions d'écran sensiblement plus lentes ou des animations scintillantes.
Pourquoi laisser le système posséder l'état de notre application, si nous pouvions le posséder nous-mêmes? Pourquoi faire des allers-retours IPC et demander au système d'ouvrir une nouvelle fenêtre, si nous pouvions simplement échanger deux vues dans notre mise en page? Pourquoi et nStop
?
Pourquoi rester coincé dans de vieilles habitudes, quand vous pouvez choisir de faciliter le raisonnement sur l'état de votre application?
L'utilisation d'activités pour modéliser des flux d'écran interdépendants est similaire. Indicateur d'intention sans activité et comportement de pile de tâches imprévisible. Mais au lieu de cela, gambader dans le monde des étendues partagées qui fournissent toujours une persistance d'état appropriée à travers la mort du processus (condition de mémoire faible).
Un merci spécial à anemomylos pour m'avoir montré La belle histoire de l'éléphant enchaîné, qui a servi d'inspiration pour cet article.
Rejoignez-nous.