Kontrollfluss in Kotlin
Kontrollfluss steuert Entscheidungen und Wiederholungen in Kotlin. Du lernst, if, when und Schleifen klar in Android-Code einzusetzen.
Kontrollfluss ist eines der ersten Kotlin-Themen, das du ständig in echter Android-Entwicklung brauchst. Du entscheidest damit, welcher Code ausgeführt wird, welcher Fall ignoriert wird und welche Arbeit mehrfach passiert. Wenn du if, when und Schleifen sauber einsetzt, wird dein Code nicht nur korrekt, sondern auch besser lesbar, leichter testbar und einfacher im Code-Review zu prüfen.
Was ist das?
Kontrollfluss beschreibt die Reihenfolge und Auswahl von Anweisungen in deinem Programm. Ohne Kontrollfluss würde dein Code starr von oben nach unten laufen. Mit Kontrollfluss reagiert er auf Zustände: Ist ein Nutzer angemeldet? Ist eine Eingabe gültig? Gibt es Netzwerkdaten? Ist eine Liste leer? Muss ein Button sichtbar sein?
In Kotlin gehören dazu vor allem if, when und Schleifen wie for und while. if prüft eine Bedingung. when wählt zwischen mehreren Fällen. Schleifen wiederholen Arbeit für mehrere Elemente oder solange eine Bedingung erfüllt ist.
Für Android ist das nicht abstrakt. Du verwendest Kontrollfluss in ViewModels, Use Cases, Repositorys, Compose-Funktionen, Tests und kleinen Hilfsfunktionen. Ein Login-Screen zeigt je nach Zustand eine Fehlermeldung, einen Ladeindikator oder den Inhalt. Eine Liste von Artikeln wird Element für Element in UI-Modelle umgewandelt. Ein Test prüft verschiedene Eingaben und erwartet unterschiedliche Ergebnisse.
Das mentale Modell ist: Dein Code beschreibt Pfade. Jeder Pfad sollte einen klaren Grund haben. Je weniger versteckte Sonderfälle es gibt, desto einfacher kannst du verstehen, debuggen und testen, was passiert.
Wie funktioniert es?
In Kotlin sind if und when nicht nur Kontrollstrukturen, sondern auch Ausdrücke. Das bedeutet: Sie können einen Wert zurückgeben. Dadurch schreibst du oft kompakteren und klareren Code, weil du eine Entscheidung direkt einer Variablen zuweisen kannst.
Ein einfaches Beispiel:
val message = if (isOnline) {
"Verbunden"
} else {
"Offline"
}
Hier ist if kein reiner Befehl, sondern liefert den Text, der in message gespeichert wird. Wichtig ist: Wenn du if als Ausdruck nutzt, brauchst du normalerweise einen else-Zweig, damit Kotlin für jeden Fall einen Wert bestimmen kann.
when ist besonders nützlich, wenn du mehrere Fälle sauber trennen willst. In Android-Code passt das gut zu Statusmodellen, etwa Loading, Success und Error. Statt verschachtelter if-Blöcke bekommst du eine klare Liste möglicher Zustände. Bei versiegelten Klassen, also sealed class oder sealed interface, kann Kotlin außerdem prüfen, ob du alle Fälle behandelst. Das ist ein großer Vorteil für Wartbarkeit: Wenn später ein neuer Zustand hinzukommt, zeigt dir der Compiler, wo du reagieren musst.
Schleifen wiederholen Code. Eine for-Schleife läuft über Elemente, zum Beispiel über eine Liste. Eine while-Schleife läuft, solange eine Bedingung wahr ist. In modernem Kotlin-Code verwendest du für Listen oft Funktionen wie map, filter oder forEach, aber Schleifen bleiben wichtig. Sie sind direkt, gut verständlich und manchmal die beste Wahl, etwa wenn du früh abbrechen willst oder mehrere Schritte bewusst nacheinander ausführst.
In Android mit Coroutines und Flow musst du besonders auf wiederholte Arbeit achten. Eine Schleife, die auf dem Main Thread sehr viele Elemente verarbeitet, kann die Oberfläche spürbar blockieren. Ein collect auf einem Flow ist ebenfalls ein Kontrollfluss über mehrere Werte über die Zeit. Auch dort entscheidest du: Was passiert bei jedem neuen Wert? Was passiert bei Fehlern? Was passiert beim Abbruch des Lebenszyklus?
Kontrollfluss ist damit eng mit Architektur verbunden. Ein ViewModel sollte Zustände vorbereiten, damit die UI möglichst klar entscheiden kann, was sie anzeigt. Eine Compose-Funktion sollte nicht voller Geschäftslogik sein. Sie darf Zustände unterscheiden, aber komplexe Entscheidungen gehören meist in eine eigene Funktion, einen Mapper oder einen Use Case.
In der Praxis
Stell dir einen Screen vor, der Profilinformationen lädt. Du möchtest in Compose je nach Zustand eine andere Oberfläche anzeigen. Dafür modellierst du den Zustand explizit und verwendest when.
sealed interface ProfileUiState {
data object Loading : ProfileUiState
data class Content(
val name: String,
val email: String,
val roles: List<String>
) : ProfileUiState
data class Error(val message: String) : ProfileUiState
}
@Composable
fun ProfileScreen(state: ProfileUiState) {
when (state) {
ProfileUiState.Loading -> {
CircularProgressIndicator()
}
is ProfileUiState.Content -> {
Column {
Text(text = state.name)
Text(text = state.email)
for (role in state.roles) {
Text(text = role)
}
}
}
is ProfileUiState.Error -> {
Text(text = state.message)
}
}
}
Dieses Beispiel zeigt drei typische Formen von Kontrollfluss. when trennt die UI-Zustände. Die for-Schleife rendert Rollen. Die einzelnen Zweige bleiben klein und verständlich. Für Lernende ist daran wichtig: Du erkennst sofort, welche Zustände existieren und welcher Code zu welchem Zustand gehört.
In einer echten App würdest du für Listen in Compose häufig LazyColumn und items verwenden. Die Schleife im Beispiel ist bewusst klein gehalten, damit das Kontrollfluss-Prinzip sichtbar bleibt. Für größere Listen wäre eine einfache for-Schleife innerhalb einer Column problematisch, weil alle Elemente auf einmal aufgebaut werden. Dann ist nicht der Kontrollfluss falsch, sondern die Wahl des UI-Werkzeugs.
Eine gute Entscheidungsregel lautet: Nutze if, wenn du eine binäre Entscheidung hast. Nutze when, wenn du mehrere klar benannte Fälle hast. Nutze Schleifen, wenn du begrenzte, verständliche Wiederholung brauchst. Wenn eine Entscheidung fachlich wichtig ist, gib ihr einen Namen und lagere sie in eine Funktion aus.
Beispiel für eine kleine fachliche Entscheidung:
fun canSubmitProfile(name: String, email: String): Boolean {
return name.isNotBlank() && email.contains("@")
}
val buttonEnabled = canSubmitProfile(name, email)
Das ist besser als eine lange Bedingung direkt im Button. Du kannst die Funktion testen, im Debugger beobachten und im Code-Review leichter diskutieren. Kontrollfluss wird dadurch nicht versteckt, sondern benannt.
Eine typische Stolperfalle ist verschachtelter Kontrollfluss. Wenn du mehrere if-Blöcke ineinander stapelst, verliert der Leser schnell den Überblick. Besonders gefährlich wird es, wenn jeder Zweig Seiteneffekte hat, also etwa Navigation auslöst, Daten speichert oder Netzwerkaufrufe startet. Dann ist schwer zu erkennen, welcher Pfad wann aktiv wird.
Ein problematisches Muster sieht so aus:
if (user != null) {
if (user.isActive) {
if (hasPermission) {
openProfile()
} else {
showPermissionError()
}
} else {
showInactiveUserError()
}
} else {
showLogin()
}
Das funktioniert, ist aber anstrengend zu lesen. Oft kannst du solche Fälle mit frühen Rückgaben oder when glätten:
fun handleProfileClick(
user: User?,
hasPermission: Boolean
) {
when {
user == null -> showLogin()
!user.isActive -> showInactiveUserError()
!hasPermission -> showPermissionError()
else -> openProfile()
}
}
Diese Form liest sich wie eine Prioritätenliste. Jeder Sonderfall steht einzeln da, der normale Pfad steht am Ende. Das ist besonders hilfreich in Event-Handlern, Validierungen und ViewModel-Methoden.
Bei Schleifen gibt es ebenfalls häufige Fehler. Ein Klassiker ist die Änderung einer Liste, während du über sie iterierst. Das kann zu Laufzeitfehlern oder übersprungenen Elementen führen. Besser ist es, eine neue Liste zu erzeugen:
val visibleItems = allItems.filter { item ->
item.isVisible
}
Hier verwendest du keinen expliziten Loop, aber der Kontrollfluss ist weiterhin da: Für jedes Element wird entschieden, ob es in die neue Liste kommt. Solche Standardfunktionen sind in Kotlin oft klarer als manuelle Schleifen.
Trotzdem solltest du nicht jede Schleife zwanghaft ersetzen. Wenn eine for-Schleife die Absicht besser zeigt, ist sie in Ordnung. Entscheidend ist nicht, ob der Code maximal kurz ist, sondern ob du ihn beim Lesen korrekt vorhersagen kannst.
Für Tests ist Kontrollfluss besonders wichtig. Jede Bedingung erzeugt mindestens zwei relevante Pfade: wahr und falsch. Bei when entstehen mehrere Pfade. Bei Schleifen gibt es oft drei interessante Fälle: keine Elemente, ein Element und mehrere Elemente. Wenn du eine Validierungsfunktion schreibst, solltest du diese Pfade gezielt testen.
Ein Beispiel für Testfälle zur Profilfreigabe:
@Test
fun emptyNameCannotSubmitProfile() {
val result = canSubmitProfile(name = "", email = "a@b.de")
assertFalse(result)
}
@Test
fun invalidEmailCannotSubmitProfile() {
val result = canSubmitProfile(name = "Mina", email = "mina")
assertFalse(result)
}
@Test
fun validInputCanSubmitProfile() {
val result = canSubmitProfile(name = "Mina", email = "mina@example.de")
assertTrue(result)
}
So prüfst du nicht nur eine Zeile Code, sondern die Denkstruktur dahinter. Du fragst: Welche Entscheidung trifft diese Funktion? Welche Eingaben führen zu welchem Pfad? Genau diese Fragen helfen dir auch beim Debuggen. Setze einen Breakpoint vor eine Bedingung, beobachte die Werte und gehe Schritt für Schritt durch die Zweige. Wenn der Code nicht den Pfad nimmt, den du erwartest, ist entweder deine Annahme falsch oder die Bedingung unklar formuliert.
In Code-Reviews solltest du bei Kontrollfluss auf drei Dinge achten. Erstens: Sind alle Fälle abgedeckt? Zweitens: Sind die Bedingungen in einer sinnvollen Reihenfolge? Drittens: Gibt es Seiteneffekte, die in einem Zweig leicht übersehen werden? Diese Fragen sind einfach, aber sie finden viele echte Fehler.
Bei Coroutines und Flow gilt zusätzlich: Kontrollfluss läuft dort oft zeitlich versetzt. Ein if vor einem Netzwerkaufruf entscheidet sofort. Ein Flow liefert dagegen später mehrere Werte. Achte darauf, Lade-, Erfolgs- und Fehlerzustände klar zu modellieren, statt sie über mehrere lose Boolean-Werte zu verteilen. Drei Booleans wie isLoading, hasError und hasData können widersprüchliche Kombinationen bilden. Ein einzelner UiState mit when ist oft robuster.
Fazit
Kontrollfluss ist die Grundlage dafür, dass dein Kotlin-Code auf Zustände, Eingaben und Listen sinnvoll reagiert. Übe bewusst mit kleinen Funktionen: Schreibe eine Validierung mit if, modelliere einen UI-Zustand mit when und teste Schleifenfälle mit leerer, kurzer und längerer Liste. Prüfe deinen Code anschließend im Debugger oder Code-Review: Jeder Pfad sollte verständlich sein, jeder Sonderfall sollte sichtbar bleiben, und keine Bedingung sollte mehr Logik tragen, als sie lesbar erklären kann.