Android Academy · Les

Moduleafhankelijkheden beheren

Houd de graaf schoon en acyclisch.

Les 3 van 413 stappen

Moduleafhankelijkheden beheren is een gratis Android Academy-les op CoddyKit. Dit is les 3 van 4. Je kunt 3 lessen uit dit leerpad gratis volledig lezen — daarna ontgrendelt CoddyKit PRO alle lessen, plus praktische oefeningen met een ingebouwde code-editor en een AI-tutor die 24/7 beschikbaar is. Deze les maakt deel uit van het leertraject Android Academy. Je voortgang wordt gesynchroniseerd op het web en in de CoddyKit-app. De cursus Android Academy bevat in totaal 4 lessen.

De afhankelijkheidsgraaf

Elke module declareert van welke andere modules deze afhankelijk is. Samen vormen deze afhankelijkheden een afhankelijkheidsgraaf. Een gezonde graaf is een DAG (gerichte acyclische graaf): afhankelijkheden wijzen in één richting en vormen nooit een lus.

In deze les leer je hoe je afhankelijkheden netjes declareert, de juiste Gradle-configuratie kiest, versies deelt en cycli voorkomt.

Een moduleafhankelijkheid declareren

Je voegt een afhankelijkheid op een andere module toe met project(":path:to:module") binnen het blok dependencies. Het pad weerspiegelt de mappen en komt overeen met wat je hebt gedeclareerd in settings.gradle.kts.

// feature/profile/build.gradle.kts
dependencies {
    implementation(project(":core:data"))
    implementation(project(":core:designsystem"))
    implementation(project(":core:model"))
}

implementation versus api

De configuratie die je kiest bepaalt wat naar gebruikers uitlekt:

  • implementation: de afhankelijkheid is privé. Modules die van jou afhankelijk zijn kunnen deze niet zien. Dit is de standaardkeuze.
  • api: de afhankelijkheid wordt opnieuw beschikbaar gesteld (transitief). Gebruik dit alleen wanneer je openbare typen uit die afhankelijkheid komen.

Geef bijna altijd de voorkeur aan implementation — dit verbetert de bouwsnelheid, omdat een wijziging in een verborgen afhankelijkheid gebruikers niet opnieuw laat compileren.

// core/data/build.gradle.kts
dependencies {
    // Repository signatures return :core:model types,
    // so consumers need to SEE it -> api
    api(project(":core:model"))

    // Network is an internal detail -> implementation
    implementation(project(":core:network"))
}

Waarom implementation het bouwen versnelt

Met implementation weet Gradle dat een wijziging in een verborgen afhankelijkheid geen invloed kan hebben op de openbare ABI van de module. Gebruikers hoeven daarom niet opnieuw te compileren. Met api verspreidt een wijziging zich door elke transitieve gebruiker.

Vuistregel: een afhankelijkheid hoort alleen in api als deze voorkomt in de openbare typen van de module (retourtypen, openbare parameters). Gebruik anders implementation.

// Public -> needs api
fun observeUser(): Flow<User>   // Flow and User leak out

// Internal -> implementation is enough
private val client: OkHttpClient // never exposed

Versies centraliseren: de versiecatalogus

Met veel modules wil je bibliotheekversies niet overal herhalen. Gradles versiecatalogus (gradle/libs.versions.toml) definieert versies en aliassen één keer. Elke module verwijst naar dezelfde alias.

# gradle/libs.versions.toml
[versions]
compose-bom = "2024.09.00"
retrofit = "2.11.0"

[libraries]
compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }
retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }

De catalogus in een module gebruiken

Modules verwijzen vervolgens naar bibliotheken via de gegenereerde libs-accessor. Zonder versienummers in het buildbestand vinden upgrades op één plek plaats.

// core/network/build.gradle.kts
dependencies {
    implementation(platform(libs.compose.bom))
    implementation(libs.retrofit)
}

De hoofdzonde: cycli

Een cyclus ontstaat wanneer module A afhankelijk is van B en B weer afhankelijk is van A (direct of via een keten). Gradle weigert een build met een circulaire afhankelijkheid. Dit wijst ook op een ontwerpprobleem: de grens tussen de twee modules klopt niet.

// :feature:cart  -> implementation(project(":feature:checkout"))
// :feature:checkout -> implementation(project(":feature:cart"))
//
// Gradle error:
// Circular dependency between the following tasks:
// :feature:cart:compile -> :feature:checkout:compile -> :feature:cart:compile

Een cyclus doorbreken

Om een cyclus te doorbreken haal je het gedeelde onderdeel uit naar een lagere module waarvan beide modules afhankelijk kunnen zijn. Als twee features elkaars gegevens nodig hebben, hoort dat gedeelde contract in core te staan, niet in een van beide features.

Zo herstel je de neerwaartse stroom: beide features wijzen omlaag naar core, en core wijst nooit terug omhoog.

// Before: cart <-> checkout (cycle)
// After:  cart -> :core:order  <-  checkout

// core/order/OrderContract.kt
data class Order(val items: List<CartItem>, val total: Double)

// feature/cart    -> implementation(project(":core:order"))
// feature/checkout-> implementation(project(":core:order"))

Inversie: afhankelijk zijn van abstracties

Soms heeft een module op een laag niveau gedrag nodig dat hogerop staat. In plaats van omhoog afhankelijk te worden, definieer je een interface in de lage module en laat je de hoge module de implementatie leveren via dependency-injectie. Dit is inversie van afhankelijkheden.

// core/analytics defines the contract
interface AnalyticsLogger {
    fun log(event: String)
}

// :app provides the real implementation and injects it down
@Module
@InstallIn(SingletonComponent::class)
object AnalyticsModule {
    @Provides
    fun logger(impl: FirebaseAnalyticsLogger): AnalyticsLogger = impl
}

De graaf visualiseren en bewaken

Je kunt Gradle vragen om de modulegraaf af te drukken of weer te geven, en zelfs een test toevoegen die de build laat mislukken als er een verboden afhankelijkheid voorkomt (bijvoorbeeld een coremodule die afhankelijk is van een feature). Tools zoals de plug-in module-graph genereren automatisch een diagram.

# Print the project structure
./gradlew projects

# Inspect why :feature:home pulls in a library
./gradlew :feature:home:dependencies --configuration debugRuntimeClasspath

Een schoon, acyclisch voorbeeld

Hier zie je een gezonde graaf. Lees deze van boven naar beneden; geen enkele pijl wijst ooit terug omhoog en geen twee modules wijzen naar elkaar. Dit is precies waar je naartoe werkt.

// :app
//   -> :feature:home   -> :core:data -> :core:network -> :core:model
//   -> :feature:profile -> :core:data -> :core:database -> :core:model
//   -> :core:designsystem
//
// Every path ends at :core:model. No cycles. Builds in parallel.

Korte controle

Je module :core:network gebruikt OkHttpClient alleen intern — deze komt nooit voor in een handtekening van een openbare functie. Welke Gradle-configuratie moet je gebruiken om de OkHttp-afhankelijkheid te declareren?

Samenvatting: moduleafhankelijkheden beheren

Je hebt geleerd hoe je de modulegraaf gezond houdt:

  • Declareer moduleafhankelijkheden met project(":path").
  • Gebruik standaard implementation; gebruik api alleen voor typen in je openbare oppervlak.
  • Centraliseer versies in een versiecatalogus (libs.versions.toml).
  • Maak nooit cycli — Gradle weigert deze; doorbreek ze door gedeelde code omlaag te verplaatsen of interfaces te gebruiken voor inversie.

Vervolgens ga je features via navigatie met elkaar verbinden zonder ze aan elkaar te koppelen.

Gratis beginnen

Leer Kotlin met een AI-tutor — gratis

Schrijf echte code en voer die uit in je browser, krijg direct hulp van een AI-tutor die 24/7 beschikbaar is en ga verder waar je gebleven bent op het web of in de app.

Cursussen
36
Lessen
152

Veelgestelde vragen

Is de les “Moduleafhankelijkheden beheren” gratis?

Ja — je kunt hier op het web alle 3 lessen van het leerpad Android Academy, waaronder “Moduleafhankelijkheden beheren”, gratis volledig lezen. Daarna ontgrendelt CoddyKit PRO alle lessen, plus interactieve oefeningen met een ingebouwde code-editor en een AI-tutor die 24/7 beschikbaar is. De cursus Android Academy bevat in totaal 4 lessen.

Wat leer ik in “Moduleafhankelijkheden beheren”?

Houd de graaf schoon en acyclisch. Je oefent met Android Academy door code rechtstreeks in de browser uit te voeren. Een AI-begeleider die 24/7 beschikbaar is beantwoordt je vragen terwijl je de les doorwerkt.

Heb ik ervaring nodig om met Android Academy te beginnen?

Ervaring vooraf is niet nodig. Android Academy op CoddyKit is opgebouwd voor beginners tot gevorderden, zodat je hier of bij het begin kunt starten en in je eigen tempo kunt leren. Dit is les 3 van 4.

Hoe lang duurt de les “Moduleafhankelijkheden beheren”?

De meeste lessen van CoddyKit duren ongeveer 5–10 minuten. Elke les is kort en interactief, zodat je gestaag vooruitgaat en op het web en in de app precies verdergaat waar je was gebleven.

Kan ik code schrijven en uitvoeren in deze les over Android Academy?

Ja. Elke les over Android Academy bevat een ingebouwde code-editor, zodat je rechtstreeks in je browser echte code kunt schrijven en uitvoeren en direct feedback van AI krijgt — lokale installatie is niet nodig.

Alle lessen in deze cursus

  1. Waarom modulariseren
  2. Feature- en coremodules
  3. Moduleafhankelijkheden beheren
  4. Navigatie tussen modules
← Terug naar Android Academy