Envisager la modularisation du projet Android
Publié: 2019-08-21Lorsqu'un projet atteint une certaine échelle, le travail ultérieur avec celui-ci dans un seul module devient moins efficace. La modulariser devient alors une solution efficace.
4 avantages de la modularisation
Avant de décider de la modularisation, il est bon d'être clair sur ce que cela implique exactement. Les avantages d'une structure de projet Android modulaire incluent :
Meilleure isolation du code
Chaque module peut exposer ses interfaces API publiques et masquer les détails d'implémentation. Avec un seul module, vous ne pouvez pas être complètement sûr que son implémentation est bien cachée (surtout dans Kotlin, où un modificateur de visibilité du package n'est pas disponible).
Évaluation plus facile des nouvelles technologies
Lorsque vous créez un module, vous pouvez vérifier le nouveau modèle d'architecture ou la nouvelle bibliothèque sans affecter les autres modules.
Réduction du temps de construction du projet
La modification d'un module nécessite la reconstruction de ce module et d'autres qui en dépendent. Voyez exactement comment cela fonctionne en lisant cette documentation : Développeurs Android. Configurations de dépendance
Travail plus pratique
Il devient plus facile d'analyser/déboguer/refactoriser des morceaux de code plus petits et isolés. De plus, l'intégration des nouveaux développeurs ira plus vite.
Ces avantages semblent suffisants pour vous convaincre de démarrer le processus de modularisation, mais par où commencer ?
#1 : Identifiez vos modules et leurs relations
Il existe deux approches pour reconnaître vos modules : par fonctionnalité et par couche.
Sous les modules de fonctionnalités, vous pouvez comprendre certaines zones de l'application disponibles pour les utilisateurs (par exemple, connexion, tableau de bord, profil, etc.). Ces zones peuvent être constituées d'un seul écran ou d'un flux d'écrans couvrant un processus. Les modules de ce type ne peuvent pas dépendre de modules du même type.
Après avoir identifié les fonctionnalités, vous devrez certainement extraire les fonctionnalités communes requises par quelques modules, voire tous. Ces modules peuvent être responsables de couches d'architecture distinctes (comme le stockage persistant, la mise en réseau, la navigation, les composants de l'interface utilisateur…) ou la logique métier de traitement de certaines données utilisées par un ensemble de fonctionnalités. Ces types de modules sont généralement appelés bibliothèques . Les modules de la bibliothèque peuvent construire des arbres de dépendance.
Outre les modules de fonctionnalités et de bibliothèques, un module est également nécessaire pour gérer les connexions horizontales entre les autres modules (plus d'informations à ce sujet au point suivant). Ce module contiendra une classe d'application personnalisée et une configuration d'injection de dépendance. Aucun autre module ne peut dépendre de ce module, mais ce module dépend de tous les autres du projet.

En tenant compte des définitions ci-dessus, la hiérarchie des modules peut ressembler à ceci :
#2 : Configuration de l'injection de dépendance
Malgré les dépendances entre les modules du projet, vous devez également configurer des dépendances dagger. Dagger propose deux façons de déclarer la dépendance : les sous- composants et la dépendance de composant .
La dépendance du sous-composant Dagger exige que les parents déclarent tous les enfants à charge. Entre les modules du projet, ce type de relation ne fonctionnerait pas, car il inverse le sens de dépendance des modules du projet. Mais il peut être utilisé dans des modules de projet distincts.

La dépendance du composant Dagger est plus flexible car un enfant peut déclarer qu'il dépend du parent. Cela permet d'utiliser ce type de dépendance entre des modules de projet distincts.
À un moment donné, vous constaterez peut-être qu'un module nécessite des connaissances limitées sur un autre module. Un très bon exemple de cela peut être la navigation entre les modules de fonctionnalités. La fourniture de ce type de relation est souvent appelée dépendance horizontale . Pour créer ce canal de communication entre des modules séparés, des modules supplémentaires avec des interfaces sont nécessaires pour décrire cette communication et un module qui liera une implémentation aux interfaces déclarées.
La configuration des dépendances du module de projet pour gérer la dépendance horizontale est présentée dans l'image ci-dessous :

Un exemple de code pour de telles relations est fourni dans un projet à la fin de l'article.
#3 : Configuration progressive
Chaque module de projet a son gradle.build, qui est à peu près le même, sauf que des dépendances et des plugins supplémentaires sont appliqués. Il est donc agréable d'extraire la configuration répétitive dans un fichier gradle à la racine du répertoire du projet. Un tel fichier peut également enregistrer des tâches de gradle courantes pour exécuter une analyse de code statique ou exécuter des tests unitaires.
Un extrait de code d'une telle configuration commune se trouve ici :
afterEvaluate { project -> def isAndroid = project.plugins.hasPlugin('com.android.library') || project.plugins.hasPlugin('com.android.application') setupModule(isAndroid) setupCommonTestDependencies(isAndroid) setupCommonTasks(isAndroid) } def setupModule(isAndroid) { if (isAndroid) { android { compileSdkVersion projectCompileSdk defaultConfig { minSdkVersion projectMinSdk targetSdkVersion projectTargetSdk } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } lintOptions { abortOnError true checkReleaseBuilds false checkAllWarnings true warningsAsErrors true def lintBaseline = file("quality/lint-baseline.xml") if (lintBaseline.exists()) baseline lintBaseline } } } else { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } } def setupCommonTestDependencies(isAndroid) { dependencies { testImplementation "junit:junit:${junitVersion}" testImplementation "org.assertj:assertj-core:${assertJVersion}" testImplementation "org.mockito:mockito-core:${mockitoVersion}" testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:${mockitoKotlinVersion}" if (isAndroid) { androidTestImplementation "androidx.test.ext:junit:${axTestJUnitVersion}" androidTestImplementation "androidx.test.espresso:espresso-core:${axEspressoLibVersion}" } } } def setupCommonTasks(isAndroid) { if (isAndroid) { tasks.register("unitTest") { task -> task.dependsOn(testDebugUnitTest) } } else { tasks.register("unitTest") { task -> task.dependsOn(test) } } }
Conclusion
Cet article n'est pas exhaustif ni un guide complet pour modulariser un projet Android. Mais je pense qu'il aborde des aspects dont vous devriez tenir compte lorsque vous démarrez la modularisation d'un projet.
Un morceau de code pour présenter une dépendance horizontale se trouve sur le lien.
Vous voulez créer une application native pour Android ? Choisissez Miquido!