Modularisation Android en 2026 : Architecture Multi-Module et Questions d'Entretien

Guide complet sur la modularisation Android en 2026. Découvrez comment structurer une architecture multi-module, utiliser les catalogues de versions Gradle, et les questions d'entretien technique essentielles.

Architecture multi-module Android avec modules feature et core

La modularisation Android transforme les bases de code monolithiques en systèmes maintenables et évolutifs où les équipes travaillent en parallèle sans conflits de code. Une application Android bien modularisée se compile plus rapidement, se teste de manière isolée et permet l'intégration de nouveaux développeurs en quelques jours plutôt qu'en semaines.

La Modularisation en Bref

La modularisation divise une application Android en modules Gradle indépendants avec des frontières claires. Un couplage faible entre les modules et une forte cohésion au sein de chaque module sont les principes directeurs. L'application de référence Now in Android de Google démontre ce pattern avec plus de 40 modules.

Pourquoi l'Architecture Multi-Module Est Essentielle pour les Applications Android

Une application Android à module unique recompile tout lorsqu'une seule ligne change. Une modification de la couche réseau déclenche la recompilation de l'interface utilisateur. Deux développeurs modifiant des fonctionnalités différentes créent des conflits de fusion dans des fichiers partagés. Les temps de compilation s'étirent à plus de 4 minutes pour les builds incrémentaux.

L'architecture multi-module répond directement à ces problèmes :

  • Compilations plus rapides : Gradle ne recompile que les modules affectés. Une modification dans :feature:checkout laisse :feature:profile intact.
  • Développement parallèle : Les équipes possèdent des modules séparés avec des API définies. Les conflits de fusion diminuent significativement.
  • Tests isolés : Les tests unitaires s'exécutent sur un seul module sans charger l'application entière.
  • Réutilisation du code : Un module de système de design :core:ui devient une bibliothèque utilisable dans plusieurs applications.

Le guide officiel de modularisation Android recommande de structurer les modules autour de fonctionnalités ou de couches qui évoluent ensemble, suivant le Principe de Responsabilité Unique.

Types de Modules et Leurs Responsabilités

Le dépôt Now in Android établit une taxonomie de modules éprouvée qui s'adapte aux applications d'entreprise.

settings.gradle.kts - Structure des moduleskotlin
include(":app")

// Modules core - utilitaires partagés
include(":core:common")
include(":core:data")
include(":core:database")
include(":core:datastore")
include(":core:designsystem")
include(":core:domain")
include(":core:model")
include(":core:network")
include(":core:ui")

// Modules feature - logique spécifique aux écrans
include(":feature:home")
include(":feature:search")
include(":feature:bookmarks")
include(":feature:settings")

Module app : Contient MainActivity, la configuration de la navigation et l'échafaudage au niveau de l'application. Dépend de tous les modules feature et des modules core requis.

Modules core : Fournissent des fonctionnalités partagées entre les features. :core:network gère les appels API. :core:database gère les entités Room. :core:designsystem définit les composants visuels de l'application.

Modules feature : Encapsulent un seul écran ou flux utilisateur. Chaque module feature dépend uniquement des modules core dont il a besoin, jamais d'autres modules feature.

Cette structure impose des dépendances unidirectionnelles : les features dépendent du core, et l'app dépend des features.

Catalogues de Versions Gradle pour la Gestion des Dépendances

Gérer les dépendances sur plus de 25 modules sans conflits de versions nécessite une configuration centralisée. Les Catalogues de Versions Gradle résolvent ce problème avec un seul fichier libs.versions.toml.

toml
# gradle/libs.versions.toml
[versions]
agp = "8.7.0"
kotlin = "2.1.0"
compose-bom = "2026.09.00"
ksp = "2.1.0-1.0.29"
hilt = "2.54"
room = "2.7.0"
retrofit = "2.11.0"

[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version = "1.15.0" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }
hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
hilt-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" }
room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" }
room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" }
room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
retrofit-core = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }

[bundles]
compose = ["androidx-compose-ui", "androidx-compose-material3"]
room = ["room-runtime", "room-ktx"]

[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
android-library = { id = "com.android.library", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }

Les modules référencent ensuite les dépendances avec des accesseurs typés :

feature/home/build.gradle.ktskotlin
dependencies {
    implementation(libs.bundles.compose)
    implementation(libs.hilt.android)
    ksp(libs.hilt.compiler)
}

Gradle génère ces accesseurs lors de la synchronisation, permettant l'autocomplétion de l'IDE et la validation à la compilation.

Prêt à réussir tes entretiens Android ?

Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.

Convention Plugins : Configuration de Build DRY

Un projet de 25 modules sans convention plugins répète les mêmes compileSdk, minSdk, composeOptions et applications de plugins dans chaque build.gradle.kts. Différents modules peuvent accidentellement utiliser des configurations différentes.

Les convention plugins centralisent cette logique dans un build inclus build-logic.

build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.ktkotlin
import com.android.build.api.dsl.LibraryExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure

class AndroidLibraryConventionPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        with(target) {
            pluginManager.apply("com.android.library")
            pluginManager.apply("org.jetbrains.kotlin.android")

            extensions.configure<LibraryExtension> {
                compileSdk = 35
                defaultConfig {
                    minSdk = 26
                    testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
                }
                compileOptions {
                    sourceCompatibility = JavaVersion.VERSION_17
                    targetCompatibility = JavaVersion.VERSION_17
                }
            }
        }
    }
}

Enregistrement du plugin dans build-logic/convention/build.gradle.kts :

build-logic/convention/build.gradle.ktskotlin
plugins {
    `kotlin-dsl`
}

dependencies {
    compileOnly(libs.android.gradlePlugin)
    compileOnly(libs.kotlin.gradlePlugin)
}

gradlePlugin {
    plugins {
        register("androidLibrary") {
            id = "myapp.android.library"
            implementationClass = "AndroidLibraryConventionPlugin"
        }
        register("androidFeature") {
            id = "myapp.android.feature"
            implementationClass = "AndroidFeatureConventionPlugin"
        }
        register("androidHilt") {
            id = "myapp.android.hilt"
            implementationClass = "AndroidHiltConventionPlugin"
        }
    }
}

Les modules feature ne nécessitent alors qu'une configuration minimale :

feature/home/build.gradle.ktskotlin
plugins {
    alias(libs.plugins.myapp.android.feature)
    alias(libs.plugins.myapp.android.hilt)
}

dependencies {
    implementation(projects.core.data)
    implementation(projects.core.designsystem)
}

Le build-logic de Now in Android de Google utilise exactement ce pattern.

Patterns de Communication Entre Modules

Les modules feature ne doivent pas dépendre directement les uns des autres. Cette contrainte préserve la compilation parallèle et évite les dépendances circulaires. Trois patterns gèrent la communication inter-features.

core/navigation/src/main/kotlin/Routes.ktkotlin
object Routes {
    const val HOME = "home"
    const val PRODUCT_DETAIL = "product/{productId}"
    const val CHECKOUT = "checkout"
    
    fun productDetail(productId: String) = "product/$productId"
}

Les modules feature importent les routes depuis :core:navigation sans savoir quel module implémente chaque destination.

Modèles de Domaine Partagés

core/model/src/main/kotlin/Product.ktkotlin
data class Product(
    val id: String,
    val name: String,
    val price: BigDecimal,
    val imageUrl: String
)

:feature:catalog et :feature:checkout dépendent tous deux de :core:model pour la classe Product.

Event Bus ou ViewModel Partagé

Pour la communication à l'exécution, un event bus partagé dans :core:common ou un ViewModel scopé gère les événements inter-features :

core/common/src/main/kotlin/CartEventBus.ktkotlin
object CartEventBus {
    private val _events = MutableSharedFlow<CartEvent>()
    val events: SharedFlow<CartEvent> = _events.asSharedFlow()
    
    suspend fun emit(event: CartEvent) {
        _events.emit(event)
    }
}

sealed interface CartEvent {
    data class ItemAdded(val productId: String) : CartEvent
    data class ItemRemoved(val productId: String) : CartEvent
}

Questions d'Entretien sur la Modularisation Android

La modularisation apparaît fréquemment dans les entretiens Android de niveau senior. Les recruteurs évaluent la compréhension des systèmes de build, des décisions d'architecture et des compromis pratiques.

Conseil d'Entretien

Lors des discussions sur la modularisation en entretien, il est recommandé de citer des métriques concrètes : améliorations des temps de build, gains de vélocité d'équipe ou nombres spécifiques de modules issus de projets réels. Les réponses abstraites sur "une meilleure séparation des préoccupations" sont moins convaincantes que "les temps de build sont passés de 4 minutes à 45 secondes après la modularisation".

Q : Quelle est la différence entre les dépendances api et implementation dans Gradle ?

Les dépendances implementation sont internes au module. Les consommateurs du module ne peuvent pas y accéder. Les dépendances api sont exposées transitivement : si le module A utilise api(libs.retrofit), le module B dépendant de A peut utiliser directement les classes Retrofit.

Règle générale : utiliser implementation par défaut. N'utiliser api que lorsque la dépendance fait partie de l'API publique du module. L'abus d'api brise l'encapsulation et ralentit les builds.

Q : Comment gérer la navigation entre des modules feature qui ne dépendent pas les uns des autres ?

Il convient de créer un module :core:navigation contenant les définitions de routes et les interfaces de navigation. Les modules feature dépendent de ce module partagé. Le module app connecte les destinations aux implémentations. Cela suit le principe d'inversion des dépendances : les features dépendent d'abstractions, pas d'implémentations concrètes.

Q : Quels sont les compromis d'avoir trop de modules ?

Chaque module ajoute une surcharge de configuration Gradle : fichiers de build, traitement des ressources, fusion des manifests. Les temps de synchronisation initiale augmentent. Le point de bascule varie selon le projet, mais 50+ modules est courant dans les grandes applications. Les bénéfices (builds parallèles, tests isolés, propriété claire) compensent généralement la surcharge pour les équipes de 5+ développeurs.

Q : En quoi les convention plugins diffèrent-ils de buildSrc ?

Les changements dans buildSrc déclenchent une reconstruction complète du projet. Les convention plugins dans un build inclus ne recompilent que lorsque le code du plugin change, pas lorsque les modules consommateurs changent. Cela rend les convention plugins plus rapides pour le développement itératif. De plus, les convention plugins peuvent être publiés comme artefact séparé pour être réutilisés dans plusieurs dépôts.

Q : Comment tester un module feature de manière isolée ?

Les modules feature dépendent des modules core via des interfaces. Il faut créer des implémentations factices dans les test fixtures :

core/data/src/testFixtures/kotlin/FakeProductRepository.ktkotlin
class FakeProductRepository : ProductRepository {
    private val products = mutableListOf<Product>()
    
    override suspend fun getProducts(): List<Product> = products
    
    fun addProduct(product: Product) {
        products.add(product)
    }
}

Les tests des modules feature injectent des fakes via les API de test de Hilt ou l'injection manuelle par constructeur.

Pour plus de questions pratiques sur l'architecture Android, consultez les questions d'entretien sur l'architecture MVVM et le module sur l'injection de dépendances.

Passe à la pratique !

Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.

Points Clés pour les Projets Android Multi-Modules

  • Structurer les modules par feature (:feature:home, :feature:checkout) et par couche (:core:data, :core:ui). Les features dépendent du core, jamais les unes des autres.
  • Utiliser les Catalogues de Versions Gradle (libs.versions.toml) pour centraliser les versions des dépendances. Les bundles regroupent les dépendances liées pour des fichiers de build plus propres.
  • Implémenter des convention plugins dans build-logic pour éliminer la configuration dupliquée entre les modules. Une modification de plugin met à jour tous les modules consommateurs.
  • Imposer des dépendances unidirectionnelles : l'app dépend des features, les features dépendent du core. Des outils comme Dependency Guard peuvent automatiser cette application.
  • Mesurer les performances de build avant et après la modularisation. Suivre les temps de build incrémentaux, pas seulement les builds propres, pour valider les améliorations.
  • Garder les frontières des modules stables. Les restructurations fréquentes annulent les bénéfices de la modularisation.
Défi du jour

Tu saurais repérer le bug en Android ?

Un vrai bout de code, un bug caché, une tentative par jour. Sans compte pour essayer.

Anthony Fillion-Maillet

Écrit par

Anthony Fillion-Maillet

Fondateur de SharpSkill

Développeur fullstack depuis plus de 10 ans. Il dirige SharpSkill et répond de tout ce qui y est publié.

Mis à jour le 17 septembre 2026

Tags

#android
#modularization
#architecture
#kotlin
#gradle

Partager

Articles similaires