Nous parcourons les modules: Navigation dans une application multi-modules avec Jetpack


Presque chaque projet en croissance commence tôt ou tard à se tourner vers l'architecture multimodulaire. Les développeurs ne veulent pas attendre que l'ensemble du projet soit réassemblé, lorsqu'une seule fonctionnalité a été modifiée. La multimodularité permet d'isoler les fonctionnalités des applications les unes des autres, réduisant ainsi le temps de génération. Mais cet isolement impose certaines restrictions sur la portée des composants. Lorsque nous utilisons la navigation à partir de Jetpack dans un projet avec un module, le graphique de navigation est disponible à partir de n'importe quel package d'application, nous pouvons toujours spécifier explicitement quelle action NavController doit effectuer, et également avoir accès à l'hôte global s'il y a des fragments imbriqués dans le projet. Mais quand il y a beaucoup de modules, des questions se posent: où construire le graphique de navigation, comment y accéder et comment ne pas se confondre dans les dépendances des modules. Nous parlerons de tout cela sous la coupe.


Graphique de navigation


La chose la plus importante à retenir lors de la conception d'une application multi-module est les dépendances. Les dépendances dans l'arborescence des dépendances du module doivent être dirigées dans une seule direction.



Le module le plus dépendant d'une application multi-modules est toujours le module d'application. Il connaît presque tous les autres modules. Dans l'application, les DI sont généralement implémentées à l'aide de différents cadres. En utilisant cette dépendance du module d'application, vous pouvez y implémenter le graphique de navigation de l'hôte principal.


Vous devez toujours vous rappeler que le module d'application doit implémenter le moins de fonctionnalités possible, car c'est le plus dépendant et presque tout changement dans le projet entraînera le réassemblage du module d'application.


Parler est bon marché. Montrez-moi le code


Et immédiatement à un exemple réel. Notre cas: le point d'entrée de l'application est l'écran de démarrage, il détermine à quel écran aller: la fonctionnalité principale ou l'autorisation. Depuis l'écran d'autorisation, il n'y a de transition que vers la fonctionnalité principale. Comme d'habitude, nous construisons un graphique de navigation - rien de compliqué.



Accès à la navigation à l'intérieur du module


Lorsque vient le temps de passer d'un écran à un autre dans un module, la question se pose - comment?


En effet, à l'intérieur du module de fonctionnalité, il n'y a pas d'accès au graphe de navigation pour obtenir l'id d'action que le NavController doit exécuter.


Ceci est résolu en implémentant DI à l'aide d'interfaces. Au lieu du module de fonctionnalité dépendant du graphique de navigation globale du module d'application, nous allons créer une interface et l'appeler WhatToNavCommandProvider, dont les variables sont des commandes de navigation.


SplashNavCommandProvider.kt


interface SplashNavCommandProvider { val toAuth: NavCommand val toMain: NavCommand } 

L'interface du fournisseur de commandes elle-même sera implémentée dans le module d'application et la classe de commandes de navigation aura les mêmes champs que les arguments de la méthode NavController.navigate


NavCommand.kt


 data class NavCommand( val action: Int, var args: Bundle? = null, val navOptions: NavOptions? = null ) 

Voyons à quoi cela ressemble dans la pratique. Depuis l'écran de démarrage, 2 transitions sont possibles: vers l'écran d'autorisation et vers l'écran fonctionnel principal. Dans le module Splash, créez une interface:


SplashNavCommandProvider.kt


 interface SplashNavCommandProvider { val toAuth: NavCommand val toMain: NavCommand } 

Dans le module d'application, nous créons une implémentation de cette interface et en utilisant le framework di (j'ai Dagger), nous la fournissons via l'interface splash au module.


SplashNavCommandProviderImpl.kt - implémentation de CommandProvider


 class SplashNavCommandProviderImpl @Inject constructor() : SplashNavCommandProvider { override val toAuth: NavCommand = NavCommand(R.id.action_splashFragment_to_authFragment) override val toMain: NavCommand = NavCommand(R.id.action_splashFragment_to_mainFragment) } 

SplashNavigationModule.kt - Module DI pour fournir des dépendances


 @Module interface SplashNavigationModule { @Binds fun bindSplashNavigator(impl: SplashNavCommandProviderImpl): SplashNavCommandProvider } 

AppActivityModule.kt - le module DI principal de l'application


 @Module interface AppActivityModule { @FragmentScope @ContributesAndroidInjector( modules = [ SplashNavigationModule::class ] ) fun splashFragmentInjector(): SplashFragment … } 

Dans le module splash, nous implémentons l'implémentation en MV (ici) soit Presenter soit ViewModel ...


SplashViewModel.kt


 class SplashViewModel @Inject constructor( private val splashNavCommandProvider: SplashNavCommandProvider ) ... 

Lorsque la logique de l'écran considère qu'il est temps de passer à un autre écran, nous transférons une commande à notre fragment et informons que nous devons passer à un autre écran.


Il serait possible d'implémenter l'implémentation de SplashNavCommandProvider directement dans le fragment, mais nous perdrons ensuite la possibilité de tester la navigation.


Dans le fragment lui-même, pour terminer la transition, vous devez obtenir NavController. Si l'écran actuel n'est pas un fragment imbriqué, nous obtenons simplement le NavController avec la méthode findNavController () et appelons la méthode de navigation dessus:


 findNavController().navigate(toMain) 

Vous pouvez le rendre un peu plus pratique en écrivant une extension pour le fragment


Framentmentxt.kt


 fun Fragment.navigate(navCommand: NavCommand) { findNavController().navigate(navCommand.action, navCommand.args, navCommand.navOptions) } 

Pourquoi juste pour le fragment? Parce que j'utilise l'approche SingleActivity, si vous en avez plusieurs, vous pouvez également créer des extensions pour Activity.


Ensuite, la navigation à l'intérieur du fragment ressemblera à ceci


 navigate(toMain) 

Fragments imbriqués


La navigation dans les fragments imbriqués peut être de deux types:


  • Transition dans un conteneur imbriqué
  • Aller dans le conteneur un ou plusieurs niveaux plus haut. Par exemple, un hôte d'activité global

Dans le premier cas, tout est simple, l'extension que nous avons écrite ci-dessus nous convient. Et pour terminer la transition dans le deuxième cas, vous devez obtenir le NavController de l'hôte souhaité. Pour ce faire, vous devez obtenir l'ID de cet hôte dans le module. Étant donné que seul le module qui implémente le graphique de navigation de cet hôte y a accès, nous allons créer une dépendance et l'implémenter dans les modules de fonctionnalité, où l'accès à un NavController spécifique est nécessaire, via Dagger.


GlobalHostModule.kt - Module DI pour fournir des dépendances globales d'ID d'hôte


 @Provides @GlobalHost fun provideGlobalHostId(): Int = R.id.host_global 

AppActivityModule.kt - le module DI principal de l'application


 @FragmentScope @ContributesAndroidInjector( modules = [ GlobalHostModule::class, ProfileNavigationModule::class, ... ] ) fun profileKnownFragmentInjector(): ProfileKnownFragment 

Incorporation d'une dépendance d'ID d'hôte dans un fragment


 @Inject @GlobalHost var hostId = 0 

Lorsque des fragments sont imbriqués, cela vaut la peine de créer un qualificatif pour chaque hôte ou d'utiliser le qualificatif existant nommé pour que Dagger comprenne quel int fournir.


Globalhost.kt


 @Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class GlobalHost 

Une fois l'ID de dépendance de l'hôte souhaité obtenu dans le fragment, vous pouvez obtenir NavController par l'ID d'hôte. Nous améliorerons notre extension pour la possibilité d'effectuer des transitions dans n'importe quel conteneur:


Framentmentxt.kt


 fun Fragment.navigate(navCommand: NavCommand, hostId: Int? = null) { val navController = if (hostId == null) { findNavController() } else { Navigation.findNavController(requireActivity(), hostId) } navController.navigate(navCommand.action, navCommand.args, navCommand.navOptions) } 

Code dans l'extrait


 navigate(toAuth, hostId) 

Ce sont les points forts de la navigation avec Jetpack dans une architecture multi-modules. Si vous avez des questions, je me ferai un plaisir d'y répondre dans les commentaires :)

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


All Articles