Modultypen: App, Feature, Core und Data sinnvoll einsetzen
Android-Projekte wachsen schnell und werden unübersichtlich. Modultypen helfen dir, Code nach Verantwortung statt nach Technik zu strukturieren.
Wenn ein Android-Projekt wächst, wächst auch die Versuchung, Code einfach irgendwo hinzuzufügen – in das nächste freie Package oder in die Klasse, die gerade offen ist. Das Ergebnis ist ein Monolith, der sich kaum testen, kaum warten und kaum im Team parallelisieren lässt. Modultypen geben dir eine klare Sprache, um deinen Code nach Verantwortung zu unterteilen, bevor das Chaos einsetzt.
Was ist das?
Ein Modul in Android ist ein eigenständiges Build-Artefakt mit eigenem build.gradle.kts, eigenem SourceSet und einer klar definierten öffentlichen Schnittstelle. Modultypen sind eine Konvention, die festlegt, welche Verantwortung ein Modul übernimmt – nicht, welche Technologie es einsetzt.
Das Google-Architektur-Team empfiehlt vier Grundtypen:
- app – Das einzige Modul, das ein Android-Manifest mit einer
Application-Klasse und allenActivity-Einstiegspunkten enthält. Es kennt alle anderen Module, besitzt selbst aber so wenig eigene Logik wie möglich. - feature – Kapselt genau eine abgeschlossene Nutzerfunktion, zum Beispiel Onboarding, Suche oder Einstellungen. Ein Feature-Modul enthält UI-Code (Composables, ViewModels) und orchestriert Daten aus data-Modulen.
- core – Stellt gemeinsam genutzten Infrastruktur-Code bereit: Design-System, Netzwerk-Client, Logging, Analytics, Test-Utilities. Core-Module haben keine Kenntnis von Features oder der konkreten App.
- data – Enthält Repositories, Datenquellen (Room, Retrofit, DataStore) und Mapping-Logik. Data-Module kennen weder UI noch andere Feature-Module.
Diese Aufteilung folgt dem Prinzip, Module nach Verantwortung statt nach beliebigen technischen Kategorien zu ordnen – so wie es die offiziellen Android-Architektur-Empfehlungen fordern.
Wie funktioniert es?
Die Modultypen erzwingen einen gerichteten Abhängigkeitsgraphen: app → feature → core/data. Diese Richtung ist entscheidend.
Ein feature-Modul darf:
- von core-Modulen abhängen (Design-System, gemeinsame Utilities)
- von data-Modulen abhängen (Repositories via Dependency Injection)
Ein feature-Modul darf nicht:
- von einem anderen feature-Modul abhängen
- von
:appabhängen
Warum ist das so wichtig? Gradle baut jedes Modul einmal und cacht das Ergebnis. Sobald ein Modul geändert wird, baut Gradle nur die direkt abhängigen Module neu. Bei sauber getrennten Typen bedeutet eine Änderung in feature:search keinerlei Rebuild von feature:onboarding. Zirkuläre oder seitwärts gerichtete Abhängigkeiten heben diesen Vorteil vollständig auf – und machen isolierte Unit-Tests nahezu unmöglich.
Für die Navigation zwischen Features empfiehlt sich ein dediziertes core:navigation-Modul, das Routen als abstrakte Datenklassen oder Sealed-Klassen definiert. So wissen Features nichts voneinander, können sich aber trotzdem gegenseitig aufrufen, ohne eine direkte Abhängigkeit einzuführen.
In der Praxis
Stell dir eine mittlere App mit drei Features vor: Login, Dashboard und Einstellungen. Ein typischer Gradle-Projektbaum sieht dann so aus:
:app
:feature:login
:feature:dashboard
:feature:settings
:core:ui
:core:navigation
:core:network
:data:user
:data:analytics
In :app/build.gradle.kts listest du alle feature-Module als implementation-Abhängigkeiten. Das Modul :feature:login hängt von :core:ui, :core:navigation und :data:user ab – nicht von :feature:dashboard.
Typische Stolperfallen
Shared UI landet im falschen Modul. Ein häufiger Fehler ist, wiederverwendbare UI-Komponenten direkt in ein Feature-Modul zu legen und sie dann aus einem anderen Feature zu importieren. Das erzeugt eine verbotene seitwärts gerichtete Abhängigkeit. Die Lösung: Solche Komponenten gehören sofort nach :core:ui.
Das app-Modul als Schrottplatz. Sobald du Logik findest, die scheinbar nirgendwo hinpasst, ist das ein Signal, dass ein neues core-Modul fehlt – nicht, dass der Code ins app-Modul gehört. Das app-Modul sollte im Idealfall nur die DI-Komponente, das Manifest und die Navigation-Graph-Verkabelung enthalten.
Hilt und Modultypen. Mit Hilt definiert jedes Modul sein eigenes @Module/@InstallIn-Objekt. Das :app-Modul trägt die @HiltAndroidApp-Annotation und bindet alle Teilgraphen zusammen. So bleibt die Verdrahtung zentralisiert, während die Implementierungen vollständig isoliert bleiben und einzeln getestet werden können.
Fazit
Modultypen sind kein akademisches Konzept – sie sind das Rückgrat jedes skalierbaren Android-Projekts. Wenn du das nächste Mal eine neue Klasse anlegst, frage dich zuerst: Ist das App-, Feature-, Core- oder Data-Verantwortung? Lege dann das passende Modul an, bevor du die Klasse schreibst. Überprüfe anschließend mit ./gradlew :app:dependencies oder dem Dependency-Analyzer in Android Studio, dass kein unerwünschter Abhängigkeitspfeil entstanden ist. Wer diesen Reflex einmal verinnerlicht hat, erkennt im Code-Review sofort, wenn Feature-Module voneinander abhängen – und weiß, warum das langfristig teuer wird.