Offizielle Architektur-Empfehlungen
Google definiert klare Schichtenprinzipien für Android-Apps. Dieser Artikel erklärt, welche Muster empfohlen werden und warum sie die Wartbarkeit verbessern.
Wenn ein Android-Projekt über die ersten Prototyp-Screens hinauswächst, stellt sich schnell eine entscheidende Frage: Wo gehört welcher Code hin? Ohne klare Struktur landet Datenbanklogik im Fragment, Netzwerkcalls in der Activity, und die Geschäftsregeln verteilen sich unsichtbar über die ganze Codebase. Google hat auf dieses Problem eine durchdachte Antwort entwickelt — die offiziellen Architektur-Empfehlungen beschreiben, wie eine moderne Android-App geschichtet sein sollte und warum diese Struktur den Unterschied zwischen technischer Schuld und langfristig wartbarem Code ausmacht.
Was ist das?
Die offiziellen Architektur-Empfehlungen von Google sind ein dokumentiertes Set an Mustern und Prinzipien, das im Android Architecture Guide beschrieben wird. Sie legen fest, wie du deine App in klar abgegrenzte Schichten aufteilst, welche Klassen für welche Aufgaben zuständig sind und welche Abhängigkeitsrichtungen erlaubt sind. Es handelt sich nicht um ein starres Framework, das du zwingend installieren musst — sondern um eine flexible Blaupause, die auf Jetpack-Bibliotheken aufbaut und sich an die tatsächliche Komplexität deiner App anpassen lässt.
Im Kern definiert Google drei Hauptschichten:
- UI-Schicht (Presentation Layer): Activities, Fragments oder Composables, die den Zustand anzeigen und Nutzereingaben entgegennehmen.
- Domain-Schicht (optional): Use Cases, die komplexe Geschäftslogik kapseln und zwischen UI und Daten vermitteln.
- Datenschicht (Data Layer): Repositories, die Datenquellen wie Room, Retrofit oder DataStore hinter einer einheitlichen Schnittstelle verbergen.
Diese Aufteilung ist kein Selbstzweck — sie ist die direkte Antwort auf reale Probleme wie schwer testbaren Spaghetti-Code, aufwändige Feature-Iterationen und undurchsichtige Bugs zur Laufzeit.
Wie funktioniert es?
Das zentrale Prinzip ist die Einwegabhängigkeit: Die UI-Schicht kennt die Domain-Schicht, die Domain-Schicht kennt die Datenschicht — aber nie umgekehrt. Daten fließen von unten nach oben als beobachtbarer Zustand, Ereignisse und Nutzeraktionen fließen von oben nach unten als Methodenaufrufe. Dieses Muster reduziert Kopplung drastisch und macht einzelne Schichten austauschbar.
ViewModel als Träger des UI-Zustands
Das ViewModel ist der offizielle Träger des UI-Zustands. Es überlebt Konfigurationsänderungen wie eine Bildschirmdrehung, kommuniziert mit Repositories oder Use Cases und stellt der UI einen beobachtbaren Zustand über StateFlow oder LiveData zur Verfügung. Die UI reagiert deklarativ auf Zustandsänderungen — sie fragt nie aktiv nach Daten, sondern sammelt sie.
Repository als einheitlicher Einstiegspunkt
Ein Repository aggregiert mehrere Datenquellen und entscheidet, wann Netzwerkdaten gecacht werden. Die UI-Schicht weiß nicht, ob Daten aus der lokalen Room-Datenbank oder aus einer REST-API kommen — sie fragt das Repository und erhält immer denselben Typ zurück. Diese Kapselung ist der Kern der Datenschicht.
Use Cases für wiederverwendbare Logik
Sobald mehrere ViewModels dieselbe Logik teilen oder eine Operation mehrere Repositories kombiniert, lohnt sich ein Use Case. Use Cases sind einfache Klassen mit einer einzigen öffentlichen Funktion — sie sind perfekt isoliert testbar und halten das ViewModel schlank.
In der Praxis
Beispiel: Artikel-Liste laden
// Domain Layer
class GetArticlesUseCase(private val repository: ArticleRepository) {
suspend operator fun invoke(): List<Article> =
repository.getArticles()
}
// UI Layer – ViewModel
class ArticlesViewModel(
private val getArticles: GetArticlesUseCase
) : ViewModel() {
private val _uiState = MutableStateFlow<ArticlesUiState>(ArticlesUiState.Loading)
val uiState: StateFlow<ArticlesUiState> = _uiState.asStateFlow()
init {
viewModelScope.launch {
_uiState.value = try {
ArticlesUiState.Success(getArticles())
} catch (e: Exception) {
ArticlesUiState.Error(e.message ?: "Unbekannter Fehler")
}
}
}
}
Dieses Muster zeigt die empfohlene Aufteilung in drei Schritten: Der Use Case enthält die Logik, das ViewModel hält den Zustand und behandelt Fehler, die Composable-UI reagiert ausschließlich auf uiState.
Typische Stolperfalle: Datenzugriff im ViewModel
Ein verbreiteter Fehler ist, Caching- oder Transformationslogik direkt im ViewModel zu implementieren. Sobald ein zweites ViewModel dieselben Daten benötigt, kopierst du Code — oder erzeugst inkonsistente Zustände, weil zwei Instanzen unabhängig voneinander denselben Endpunkt aufrufen. Die Regel ist klar: Alles, was mit dem Laden, Speichern oder Transformieren von Daten zu tun hat, gehört ins Repository oder in einen Use Case. Das ViewModel ist ausschließlich für die Aufbereitung des UI-Zustands zuständig.
Testbarkeit als Qualitätsmerkmal
Google betont Testbarkeit als zentrales Ziel der empfohlenen Architektur. Wenn du die Schichten sauber trennst, kannst du jede unabhängig testen:
- Repository mit einer In-Memory-Room-Datenbank oder einem gefakten HTTP-Client.
- Use Case mit einem gemockten Repository via Mockito oder Fake-Klassen.
- ViewModel mit einem gefakten Use Case und der
kotlinx-coroutines-test-Bibliothek und ihremTestCoroutineDispatcher. - UI mit Compose-Snapshot-Tests oder Espresso für End-to-End-Szenarien.
Dieser mehrschichtige Testansatz ist Teil der offiziellen Android-Quality-Richtlinien und wird in CI-Pipelines erwartet. Ein Projekt, das nach Googles Empfehlungen strukturiert ist, lässt sich nahezu friktionslos in einen automatisierten Build-Prozess integrieren — weil die Schichten klare Grenzen haben, an denen sich Test-Doubles einsetzen lassen.
Fazit
Die offiziellen Architektur-Empfehlungen von Google sind kein optionales Extra, sondern die Grundlage für jeden Code, den du langfristig warten oder im Team entwickeln möchtest. Schichten trennen Verantwortlichkeiten, Repositories kapseln Datenquellen, ViewModels halten den UI-Zustand — und jedes Element ist isoliert testbar. Nimm dir eine bestehende Activity oder ein Fragment aus deinem aktuellen Projekt und prüfe aktiv: Enthält es direkten Datenbankzugriff, Netzwerkcalls oder Geschäftslogik? Falls ja, hast du deinen nächsten Refactoring-Kandidaten gefunden. Extrahiere die Logik Schritt für Schritt in ein Repository oder einen Use Case, schreibe einen Unit-Test dafür und beobachte, wie dein Code klarer, robuster und einfacher erweiterbar wird.