Architecture Capstone: Alle Schichten im Zusammenspiel
Alle Architekturschichten vereinen sich in einer kleinen App. UI, Domain, Daten, DI und Tests arbeiten gemeinsam.
Nachdem du UI-, Domain- und Datenschicht, Dependency Injection und Tests einzeln kennengelernt hast, kommt jetzt die entscheidende Übung: alle Teile gleichzeitig in einer kleinen, aber vollständigen App zusammenführen. Das Architecture Capstone ist kein neues Konzept, das du noch lernen müsstest – es ist der Moment, in dem du die moderne Android-Architektur zum ersten Mal als zusammenhängendes System erlebst und begreifst, warum jede Schicht genau so gebaut ist, wie sie ist.
Was ist das?
Ein Architecture Capstone ist eine bewusst kleine Anwendung – typischerweise eine Feature-App mit wenigen Screens –, die alle Bausteine der modernen Android-Architektur gleichzeitig einsetzt: eine UI-Schicht mit Compose und ViewModels, eine optionale Domain-Schicht mit Use Cases, eine Datenschicht mit Room und einer Remote-Datenquelle, Dependency Injection via Hilt sowie ein vollständiges Testset auf Unit- und Integrationsbasis.
Das Ziel ist nicht maximale Feature-Vielfalt, sondern korrekte Verdrahtung. Googles offizielle Architecture Recommendations beschreiben diesen Reifegrad als „production-ready”: Jede Schicht kennt ihre Verantwortung, Daten fließen unidirektional von der Datenschicht in Richtung UI, und keine Schicht verletzt die Abhängigkeitsregeln. Wer eine Todo-App oder eine News-Feed-App nach diesem Maßstab baut, kann dieselben Muster auf jede produktive Android-App übertragen.
Wie funktioniert es?
Die drei Schichten bilden eine klare Hierarchie. Die UI-Schicht (Composables + ViewModel) kennt ausschließlich UI-States und Events. Das ViewModel hält StateFlow-Objekte, die aus Use Cases oder direkt aus Repositories befüllt werden. Es besitzt keinerlei Wissen über Netzwerk oder Datenbank. Die Domain-Schicht ist optional, aber ab mittlerer Komplexität sinnvoll: Use-Case-Klassen kapseln Geschäftsregeln und hängen ausschließlich von Repository-Interfaces ab. Die Datenschicht stellt dieses Repository bereit und koordiniert intern Room (lokal) und Retrofit (remote).
Offline-First ist das Verbindungsglied zwischen Datenschicht und UI: Das Repository liest immer aus der lokalen Room-Datenbank (Single Source of Truth). Ein Coroutine-Job oder WorkManager-Task synchronisiert im Hintergrund mit dem Server. Die UI abonniert nur den Room-Flow – ob gerade eine Netzwerkverbindung besteht oder nicht, ist für das ViewModel irrelevant.
Hilt löst das „Wer erstellt wen?”-Problem. Mit @HiltViewModel, @Inject constructor und Hilt-Modulen (@InstallIn(SingletonComponent::class)) werden alle Abhängigkeiten automatisch eingebettet. Das erlaubt es, in Tests jede Abhängigkeit durch eine Fake-Implementierung zu ersetzen, ohne den Produktionscode anzufassen.
In der Praxis
Ein typisches Capstone-Projekt ist eine einfache Artikelliste. Hier das Zusammenspiel von Repository, Use Case und ViewModel in kompakter Form:
// Datenschicht: Room + Remote
class ArticleRepositoryImpl @Inject constructor(
private val dao: ArticleDao,
private val api: ArticleApi
) : ArticleRepository {
override fun getArticles(): Flow<List<Article>> = dao.observeAll()
override suspend fun refresh() {
val remote = api.fetchArticles()
dao.upsertAll(remote.map { it.toEntity() })
}
}
// Domain-Schicht: Use Case
class GetArticlesUseCase @Inject constructor(
private val repo: ArticleRepository
) {
operator fun invoke(): Flow<List<Article>> = repo.getArticles()
}
// UI-Schicht: ViewModel
@HiltViewModel
class ArticleViewModel @Inject constructor(
getArticles: GetArticlesUseCase
) : ViewModel() {
val articles: StateFlow<List<Article>> =
getArticles()
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = emptyList()
)
}
Der entscheidende Punkt: Das ViewModel kennt nur GetArticlesUseCase. Es hat keine import-Zeile für Room, Retrofit oder irgendeinen Context. Diese strikte Grenze ist kein Stilmittel – sie ist die Voraussetzung dafür, dass Unit-Tests überhaupt funktionieren.
Typische Stolperfalle: Viele Anfänger instanziieren das Room-@Database-Objekt direkt im ViewModel oder übergeben den Context manuell durch mehrere Schichten. Das erzeugt eine unsichtbare Kopplung: Unit-Tests brauchen plötzlich einen Android-Emulator, Laufzeiten werden länger, und ein einfaches Repository-Swap ist nicht mehr möglich. Die Lösung ist konsequente DI: Hilt erzeugt die Datenbankinstanz in einem @Module, und das ViewModel bezieht sie ausschließlich über das Interface.
Für das Testen empfiehlt sich eine Zwei-Ebenen-Strategie: Unit-Tests für ViewModels und Use Cases arbeiten mit einem FakeArticleRepository, das vorberechnete Listen zurückgibt und ArticleRepository implementiert. Integrationstests mit Hilt-Testkomponenten und einer In-Memory-Room-Datenbank prüfen das Zusammenspiel von DAO und echtem Coroutine-Dispatcher. Damit deckst du beide Ebenen ab, ohne einen echten Server anzusprechen, und hältst die Testsuite schnell.
Fazit
Das Architecture Capstone ist der Punkt auf der Roadmap, an dem du aufhörst, Konzepte isoliert zu üben, und beginnst, echte App-Qualität zu liefern. Nimm dir eine kleine App – eine Artikelliste, eine lokale Notiz-App, einen einfachen Wetterdienst – und verdrahte alle Schichten bewusst nach dem offiziellen Android-Architektur-Guide. Schreib im Anschluss mindestens einen Unit-Test pro Use Case und einen Integrationstest für dein Repository. Wenn alle Tests grün sind und dein ViewModel in keiner Zeile direkt auf Room oder Retrofit verweist, hast du das Capstone bestanden – und das Fundament gelegt, auf dem jede weitere Android-App aufbaut.