Android Modularisatie in 2026: Multi-Module Architectuur en Sollicitatievragen

Best practices voor Android modularisatie met convention plugins, Gradle version catalogs en feature modules. Inclusief sollicitatievragen voor senior ontwikkelaars.

Android Modularisatie in 2026: Multi-Module Architectuur en Sollicitatievragen

Android modularisatie transformeert monolithische codebases in onderhoudbare, schaalbare systemen waar teams parallel kunnen werken zonder elkaars code te beïnvloeden. Een goed gemodulariseerde Android-app bouwt sneller, test geïsoleerd en stelt nieuwe ontwikkelaars in staat om binnen dagen productief te zijn in plaats van weken.

Modularisatie in een Notendop

Modularisatie splitst een Android-app in onafhankelijke Gradle-modules met duidelijke grenzen. Lage koppeling tussen modules en hoge cohesie binnen elke module zijn de leidende principes. Googles Now in Android referentie-app demonstreert dit patroon met meer dan 40 modules.

Waarom Multi-Module Architectuur Belangrijk is voor Android-Apps

Een Android-app met één enkele module hercompileert alles wanneer één regel verandert. Een wijziging aan de netwerklaag activeert hercompilatie van de UI. Twee ontwikkelaars die verschillende features bewerken creëren merge-conflicten in gedeelde bestanden. Buildtijden rekken op tot meer dan 4 minuten bij incrementele builds.

Multi-module architectuur pakt deze pijnpunten direct aan:

  • Snellere builds: Gradle hercompileert alleen getroffen modules. Een wijziging in :feature:checkout laat :feature:profile onaangetast.
  • Parallelle ontwikkeling: Teams bezitten aparte modules met gedefinieerde APIs. Merge-conflicten nemen significant af.
  • Geïsoleerde tests: Unit tests draaien tegen één module zonder de hele app te laden.
  • Code hergebruik: Een :core:ui design system module wordt een bibliotheek die in meerdere apps gebruikt kan worden.

De officiële Android modularisatie-gids raadt aan om modules te structureren rond features of lagen die samen veranderen, volgend op het Single Responsibility Principle.

Moduletypes en Hun Verantwoordelijkheden

De Now in Android repository vestigt een bewezen module-taxonomie die schaalt naar enterprise-apps.

settings.gradle.kts - Modulestructuurkotlin
include(":app")

// Core modules - gedeelde utilities
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")

// Feature modules - schermspecifieke logica
include(":feature:home")
include(":feature:search")
include(":feature:bookmarks")
include(":feature:settings")

App module: Bevat MainActivity, navigatie-setup en app-level scaffolding. Is afhankelijk van alle feature modules en benodigde core modules.

Core modules: Bieden gedeelde functionaliteit over features heen. :core:network behandelt API-aanroepen. :core:database beheert Room entities. :core:designsystem definieert de visuele componenten van de app.

Feature modules: Omvatten één enkel scherm of gebruikersstroom. Elke feature module is alleen afhankelijk van de core modules die het nodig heeft, nooit van andere feature modules.

Deze structuur dwingt unidirectionele afhankelijkheden af: features zijn afhankelijk van core, en app is afhankelijk van features.

Gradle Version Catalogs voor Dependency Management

Het beheren van dependencies over 25+ modules zonder versieconflicten vereist gecentraliseerde configuratie. Gradle Version Catalogs lossen dit op met één enkel libs.versions.toml bestand.

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" }

Modules refereren vervolgens dependencies met type-safe accessors:

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

Gradle genereert deze accessors tijdens synchronisatie, wat IDE-autocompletie en compile-time validatie mogelijk maakt.

Klaar om je Android gesprekken te halen?

Oefen met onze interactieve simulatoren, flashcards en technische tests.

Convention Plugins: DRY Build Configuratie

Een project met 25 modules zonder convention plugins herhaalt dezelfde compileSdk, minSdk, composeOptions en plugin-toepassingen in elke build.gradle.kts. Verschillende modules kunnen per ongeluk verschillende configuraties gebruiken.

Convention plugins centraliseren deze logica in een build-logic included build.

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
                }
            }
        }
    }
}

Registreer de plugin in 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"
        }
    }
}

Feature modules vereisen nu minimale configuratie:

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)
}

Googles Now in Android build-logic gebruikt precies dit patroon.

Module Communicatiepatronen

Feature modules mogen niet direct van elkaar afhankelijk zijn. Deze beperking behoudt parallelle compilatie en voorkomt circulaire afhankelijkheden. Drie patronen behandelen cross-feature communicatie.

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"
}

Feature modules importeren routes van :core:navigation zonder te weten welke module welke bestemming implementeert.

Gedeelde Domain Modellen

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

Zowel :feature:catalog als :feature:checkout zijn afhankelijk van :core:model voor de Product class.

Event Bus of Shared ViewModel

Voor runtime communicatie behandelt een gedeelde event bus in :core:common of een scoped ViewModel cross-feature events:

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
}

Sollicitatievragen over Android Modularisatie

Modularisatie komt vaak voor in senior Android-sollicitaties. Interviewers beoordelen begrip van build-systemen, architectuurbeslissingen en praktische trade-offs.

Sollicitatietip

Bij het bespreken van modularisatie in sollicitatiegesprekken is het belangrijk om naar concrete metrieken te verwijzen: buildtijdverbeteringen, teamsnelheidswinsten of specifieke moduleaantallen uit projecten. Abstracte antwoorden over "betere scheiding van verantwoordelijkheden" zijn minder overtuigend dan "buildtijden daalden van 4 minuten naar 45 seconden na modularisatie."

V: Wat is het verschil tussen api en implementation dependencies in Gradle?

implementation dependencies zijn intern aan de module. Consumenten van de module kunnen er niet bij. api dependencies worden transitief blootgesteld: als module A api(libs.retrofit) gebruikt, kan module B die afhankelijk is van A direct Retrofit classes gebruiken.

Vuistregel: gebruik standaard implementation. Gebruik alleen api wanneer de dependency deel uitmaakt van de publieke API van de module. Overmatig gebruik van api breekt encapsulatie en vertraagt builds.

V: Hoe wordt navigatie tussen feature modules afgehandeld die niet van elkaar afhankelijk zijn?

Maak een :core:navigation module met route-definities en navigatie-interfaces. Feature modules zijn afhankelijk van deze gedeelde module. De app module koppelt bestemmingen aan implementaties. Dit volgt het Dependency Inversion Principle: features zijn afhankelijk van abstracties, niet van concrete implementaties.

V: Wat zijn de trade-offs van te veel modules?

Elke module voegt Gradle configuratie-overhead toe: buildbestanden, resourceverwerking, manifest merging. Initiële sync-tijden nemen toe. Het kantelpunt varieert per project, maar 50+ modules is gebruikelijk bij grote apps. De voordelen (parallelle builds, geïsoleerde tests, duidelijke ownership) wegen typisch op tegen de overhead voor teams van 5+ ontwikkelaars.

V: Hoe verschillen convention plugins van buildSrc?

buildSrc wijzigingen triggeren een volledige project rebuild. Convention plugins in een included build hercompileren alleen wanneer de plugin code verandert, niet wanneer consumerende modules veranderen. Dit maakt convention plugins sneller voor iteratieve ontwikkeling. Daarnaast kunnen convention plugins worden gepubliceerd als apart artifact voor hergebruik over repositories.

V: Hoe test je een feature module geïsoleerd?

Feature modules zijn afhankelijk van core modules via interfaces. Maak fake implementaties in 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)
    }
}

Feature module tests injecteren fakes via Hilts testing APIs of handmatige constructor injection.

Voor meer oefenvragen over Android-architectuur, zie de MVVM architectuur sollicitatievragen en de dependency injection module.

Begin met oefenen!

Test je kennis met onze gespreksimulatoren en technische tests.

Kernpunten voor Multi-Module Android Projecten

  • Structureer modules op feature (:feature:home, :feature:checkout) en laag (:core:data, :core:ui). Features zijn afhankelijk van core, nooit van elkaar.
  • Gebruik Gradle Version Catalogs (libs.versions.toml) om dependency-versies te centraliseren. Bundles groeperen gerelateerde dependencies voor schonere buildbestanden.
  • Implementeer convention plugins in build-logic om gedupliceerde configuratie over modules te elimineren. Eén plugin-wijziging werkt alle consumerende modules bij.
  • Dwing unidirectionele afhankelijkheden af: app is afhankelijk van features, features zijn afhankelijk van core. Tools zoals Dependency Guard kunnen handhaving automatiseren.
  • Meet buildprestaties voor en na modularisatie. Track incrementele buildtijden, niet alleen clean builds, om verbeteringen te valideren.
  • Houd modulegrenzen stabiel. Frequente herstructurering negeert de voordelen van modularisatie.
Dagelijkse challenge

Zie jij de bug in Android?

Een echt codefragment, een verborgen bug, één poging per dag. Zonder account uit te proberen.

Anthony Fillion-Maillet

Geschreven door

Anthony Fillion-Maillet

Oprichter van SharpSkill

Al meer dan 10 jaar fullstack-ontwikkelaar. Hij leidt SharpSkill en staat in voor alles wat hier verschijnt.

Bijgewerkt op 17 september 2026

Delen

Gerelateerde artikelen