Android Coden
Android 5 min lesen

MVVM in Android

MVVM trennt UI von Logik und ist Googles empfohlenes Architekturmuster für Android. Dieser Artikel erklärt ViewModel, State und häufige Stolperfallen.

MVVM ist das Architekturmuster, das Google für Android-Apps empfiehlt — und das aus gutem Grund. Wer einmal erlebt hat, wie eine monolithische Activity bei jeder Bildschirmrotation abstürzt oder wie ein Fragment seinen Zustand verliert, versteht sofort: Eine klare Trennung zwischen Daten und Darstellung ist keine akademische Übung, sondern tägliche Notwendigkeit im professionellen Android-Alltag.

Was ist das?

MVVM steht für Model–View–ViewModel. Das Muster teilt eine Anwendung in drei klar abgegrenzte Schichten auf:

  • Model enthält die Geschäftslogik, Datenzugriffe und Domain-Objekte — alles, was unabhängig von der UI existiert. Typischerweise sind das Repositories und Use Cases.
  • View ist ausschließlich für die Darstellung zuständig. In modernem Android ist das eine Activity, ein Fragment oder eine Composable-Funktion. Die View beobachtet den Zustand und rendert ihn, trifft aber keine Entscheidungen über Daten oder Navigation.
  • ViewModel ist die Verbindung zwischen beiden. Er hält den UI-Zustand als beobachtbare Datenströme, verarbeitet Nutzerinteraktionen und delegiert Aufgaben an das Model — ohne dabei selbst eine direkte Referenz auf die View zu halten.

Das entscheidende Merkmal des ViewModels: Er überlebt Konfigurationsänderungen. Dreht der Nutzer das Gerät, wird die Activity vom System neu erzeugt — der ViewModel aber bleibt am Leben und hält den Zustand unverändert. Genau das macht das Muster so wertvoll im Android-Alltag, denn Konfigurationsänderungen können jederzeit auftreten und ließen sich früher nur mit unzuverlässigem onSaveInstanceState abfangen.

Wie funktioniert es?

Das Android-Jetpack liefert androidx.lifecycle.ViewModel als Basisklasse. Du leitest deine eigene Klasse davon ab und hältst den Zustand als MutableStateFlow, den du nach außen als schreibgeschützten StateFlow exportierst:

class CounterViewModel : ViewModel() {
    private val _count = MutableStateFlow(0)
    val count: StateFlow<Int> = _count.asStateFlow()

    fun increment() {
        _count.update { it + 1 }
    }
}

In Jetpack Compose rufst du den ViewModel über viewModel() ab und sammelst den Flow mit collectAsStateWithLifecycle(), das den Collection-Scope automatisch an den Lifecycle der Composable bindet:

@Composable
fun CounterScreen(viewModel: CounterViewModel = viewModel()) {
    val count by viewModel.count.collectAsStateWithLifecycle()

    Column {
        Text("Zähler: $count")
        Button(onClick = { viewModel.increment() }) {
            Text("Erhöhen")
        }
    }
}

Die Richtung des Datenflusses ist dabei immer einbahnig: Der Nutzer löst ein Ereignis aus → der ViewModel verarbeitet es → der neue Zustand fließt als StateFlow zurück → die Composable-Funktion rendert sich neu. Dieses Prinzip nennt sich Unidirectional Data Flow (UDF) und ist der Kern der offiziellen Android-Architekturempfehlungen. Es macht den Zustand jederzeit vorhersehbar und leicht nachverfolgbar.

Für asynchrone Operationen nutzt du viewModelScope, einen Coroutine-Scope, der automatisch abgebrochen wird, wenn der ViewModel gelöscht wird:

fun loadData() {
    viewModelScope.launch {
        val result = repository.fetchItems()
        _items.value = result
    }
}

In der Praxis

Zustand als einzelne State-Klasse bündeln

In einem produktiven Screen modellierst du den gesamten UI-Zustand als eine einzige data class. Das hält alle Zustandsänderungen an einem Ort und macht Lade- und Fehlerzustände explizit sichtbar:

data class ArticleUiState(
    val articles: List<Article> = emptyList(),
    val isLoading: Boolean = false,
    val error: String? = null
)

class ArticleViewModel(private val repository: ArticleRepository) : ViewModel() {
    private val _uiState = MutableStateFlow(ArticleUiState())
    val uiState: StateFlow<ArticleUiState> = _uiState.asStateFlow()

    init { loadArticles() }

    private fun loadArticles() {
        viewModelScope.launch {
            _uiState.update { it.copy(isLoading = true) }
            try {
                val articles = repository.getArticles()
                _uiState.update { it.copy(articles = articles, isLoading = false) }
            } catch (e: Exception) {
                _uiState.update { it.copy(error = e.message, isLoading = false) }
            }
        }
    }
}

Stolperfalle: Context direkt im ViewModel speichern

Ein sehr verbreiteter Fehler für Einsteiger ist es, einen Context-Verweis als Feld im ViewModel zu halten. Da der ViewModel die Activity überlebt, entsteht dabei ein Memory Leak: Die Activity kann nicht vom Garbage Collector freigegeben werden, solange der ViewModel auf sie zeigt. Wenn du den Application-Kontext wirklich benötigst, leite stattdessen von AndroidViewModel ab und rufe getApplication<Application>() auf. Besser noch: Lagere kontextabhängige Operationen in ein Repository aus, das du per Dependency Injection (z. B. Hilt) bereitstellst. So bleibt der ViewModel schlank und vollständig testbar.

Einmalige Ereignisse nicht als StateFlow modellieren

Navigation oder Toast-Meldungen sind Side Effects — sie sollen genau einmal ausgeführt werden, nicht bei jeder Recomposition erneut. Modellierst du sie als normalen StateFlow, empfängt ein neu gestarteter Collector das zuletzt gespeicherte Ereignis noch einmal. Nutze stattdessen einen Channel mit BUFFERED-Kapazität und lese ihn in der UI über receiveAsFlow() aus. Das stellt sicher, dass jedes Ereignis genau einmal konsumiert wird.

Fazit

MVVM mit ViewModel und StateFlow ist kein optionales Muster — es ist die Grundlage, auf der jede stabile Android-App aufbaut. Sobald du die drei Schichten verinnerlicht und den unidirektionalen Datenfluss verstanden hast, wird es deutlich einfacher, Fehler zu isolieren und Tests zu schreiben. Um dein Verständnis aktiv zu festigen, nimm eine bestehende Activity, die UI-Zustand direkt als Felder hält, und refaktoriere sie schrittweise: Verschiebe den Zustand in einen neuen ViewModel, ersetze die Felder durch einen StateFlow und schreibe einen Unit-Test, der prüft, ob eine Nutzeraktion den erwarteten Zustand erzeugt. Du wirst schnell merken, wie viel übersichtlicher der Code wird — und wie selten du dabei noch ein echtes Gerät brauchst.

Quellen (4)
Redaktion

Geschrieben von

Redaktion

Das Redaktionsteam recherchiert und schreibt Artikel zu aktuellen Themen rund um Tech, Lifestyle und Ratgeber.