Variablen und Werte in Kotlin
Du lernst, wann du in Kotlin val oder var nutzt. So hältst du Android-Code stabiler, klarer und leichter testbar.
Variablen und Werte gehören zu den ersten Kotlin-Themen, die du lernst. Gleichzeitig bleiben sie bis in professionellen Android-Code wichtig. Der Kern ist einfach zu merken: Verwende val als Standard und greife nur zu var, wenn sich ein Zustand wirklich ändern muss. Diese Entscheidung beeinflusst Lesbarkeit, Fehlersuche, Tests, Compose-State und die Art, wie du Daten durch deine App bewegst.
Was ist das?
In Kotlin bindest du einen Namen an einen Wert. Dieser Name kann mit val oder var deklariert werden. Mit val sagst du: Dieser Name wird nach der Initialisierung nicht neu zugewiesen. Mit var sagst du: Dieser Name darf später einen anderen Wert bekommen.
Das Wort „Variable“ wird im Alltag oft für beides verwendet, aber für dein Denken ist die Unterscheidung wichtig. Ein val ist kein Versprechen, dass das Objekt dahinter in jedem Fall komplett unveränderlich ist. Es bedeutet zuerst: Die Referenz, also die Bindung des Namens, bleibt gleich. Wenn du etwa eine mutable Liste in einem val speicherst, kannst du weiterhin Elemente hinzufügen, weil die Liste selbst veränderlich ist. Du kannst aber den Namen nicht auf eine andere Liste zeigen lassen. Diese Feinheit ist ein häufiger Stolperstein.
Für Android-Entwicklung ist diese Unterscheidung mehr als Syntax. Eine App besteht aus vielen Datenflüssen: Nutzereingaben, UI-Zustand, Netzwerkantworten, Datenbankinhalte, Navigation, Berechtigungen und Lebenszyklusereignisse. Wenn überall Werte verändert werden können, wird dein Code schwer vorhersagbar. Du musst dann beim Lesen ständig fragen: Wer kann diesen Wert ändern? Wann passiert das? Passiert es vor oder nach diesem Callback? Genau hier hilft val: Es reduziert die Anzahl der Dinge, die du im Kopf behalten musst.
Das mentale Modell für Anfänger lautet: Ein val ist eine stabile Aussage im aktuellen Kontext. Ein var ist ein veränderlicher Zustand, der beobachtet, aktualisiert oder geschützt werden muss. Je größer dein Code wird, desto wertvoller werden stabile Aussagen. Sie machen Funktionen verständlicher, ViewModels robuster und Tests klarer. Darum ist val in Kotlin nicht nur eine nette Abkürzung, sondern ein Grundbaustein für sauberen Android-Code.
In der Roadmap passt dieses Thema an den Anfang deiner Kotlin-Grundlagen, weil viele spätere Konzepte darauf aufbauen. Null-Sicherheit, Datenklassen, Funktionen, Collections, Coroutines, Flow und Compose-State fühlen sich deutlich klarer an, wenn du sauber zwischen unveränderlichen Werten und veränderlichem Zustand unterscheidest. Du lernst also nicht nur zwei Schlüsselwörter, sondern eine Arbeitsweise.
Wie funktioniert es?
Die Syntax ist kurz:
val userName = "Mira"
var retryCount = 0
userName kann nach dieser Zeile nicht neu zugewiesen werden. retryCount darf später geändert werden, zum Beispiel mit retryCount += 1. Kotlin kann den Typ meist ableiten. Du musst also nicht immer String oder Int hinschreiben. Wenn es der Lesbarkeit hilft oder die Initialisierung später erfolgt, kannst du den Typ aber explizit angeben:
val maxRetries: Int = 3
var selectedTabIndex: Int = 0
Ein val muss genau einmal initialisiert werden, bevor du es verwendest. Das kann direkt in der Deklaration passieren oder in bestimmten Fällen später, etwa in einem Konstruktor. Danach ist keine neue Zuweisung erlaubt. Der Compiler schützt dich hier aktiv. Wenn du versuchst, einem val später einen anderen Wert zu geben, bekommst du einen Fehler, bevor die App überhaupt läuft.
Bei var ist der Compiler weniger streng, weil du ausdrücklich Änderbarkeit erlaubst. Das bedeutet aber auch: Du übernimmst mehr Verantwortung. Jede spätere Änderung kann Auswirkungen auf nachfolgenden Code haben. In kleinen Beispielen ist das kaum spürbar. In echten Android-Apps mit asynchronem Code, mehreren UI-Zuständen und Lebenszykluswechseln kann es schnell unübersichtlich werden.
Wichtig ist außerdem der Unterschied zwischen Neuzuweisung und Objektänderung. Dieses Beispiel zeigt die Falle:
val names = mutableListOf("Ali", "Mira")
names.add("Noah") // erlaubt
// names = mutableListOf("Lea") // nicht erlaubt
Der Name names bleibt auf dieselbe Liste gebunden. Die Liste selbst ist aber veränderlich. Wenn du wirklich verhindern willst, dass anderer Code die Liste ändert, solltest du unveränderliche Schnittstellen verwenden, zum Beispiel List<String> statt MutableList<String>. Auch hier ist val nur ein Teil der Lösung. Saubere Mutability entsteht durch mehrere Entscheidungen: val für stabile Bindungen, immutable Datentypen für stabile Inhalte und klare Zuständigkeiten für Änderungen.
Im Android-Alltag taucht dieses Thema an vielen Stellen auf. In einer Funktion nutzt du oft val, um berechnete Zwischenergebnisse festzuhalten. In einem ViewModel hältst du internen Zustand manchmal als var oder über State-Holder, gibst nach außen aber bevorzugt unveränderliche Werte oder nur lesbare Streams frei. In Compose verwendest du remember und State-APIs, wenn die UI auf Änderungen reagieren soll. Dort ist Veränderlichkeit nicht verboten, aber sie muss sichtbar und korrekt modelliert sein, damit Recomposition funktioniert.
Ein gutes Beispiel ist UI-State. Der Bildschirm kann einen Ladezustand, Daten oder eine Fehlermeldung anzeigen. Du könntest viele einzelne var-Felder anlegen. Besser ist oft ein einzelner unveränderlicher State-Wert, der bei Änderungen durch eine neue Kopie ersetzt wird. Das passt gut zu Datenklassen und macht klar, was der Bildschirm gerade wissen muss.
val hilft auch bei Nebenläufigkeit. Android-Code arbeitet häufig mit Coroutines. Wenn ein Wert innerhalb einer Coroutine unverändert bleibt, ist es leichter zu verstehen, was passiert. Veränderliche Werte, die von mehreren Stellen gelesen oder geschrieben werden, brauchen dagegen besondere Aufmerksamkeit. Du musst dann klären, in welchem Scope sie leben, wer sie ändern darf und ob Updates in der richtigen Reihenfolge ankommen.
Die Entscheidungsregel ist daher: Beginne mit val. Wechsle zu var, wenn du einen konkreten Änderungsfall benennen kannst. „Vielleicht brauche ich später eine Änderung“ ist kein guter Grund. Wenn später wirklich Änderung nötig wird, kannst du gezielt umbauen. Dieser Stil trainiert dich darauf, Zustand bewusst zu modellieren, statt ihn zufällig entstehen zu lassen.
In der Praxis
Stell dir einen einfachen Login-Bildschirm in Jetpack Compose vor. Der Nutzer gibt eine E-Mail-Adresse und ein Passwort ein. Diese Eingaben ändern sich, also brauchst du veränderlichen UI-State. Gleichzeitig gibt es viele Werte, die in einer Funktion stabil bleiben sollten: Labels, berechnete Flags oder Ergebnisse einer Validierung. Du kombinierst also beides.
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.material3.Button
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.foundation.layout.Column
@Composable
fun LoginForm(
onLoginClick: (email: String, password: String) -> Unit
) {
var email by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
val isEmailValid = email.contains("@")
val isPasswordValid = password.length >= 8
val canSubmit = isEmailValid && isPasswordValid
Column {
OutlinedTextField(
value = email,
onValueChange = { newValue ->
email = newValue
},
label = { Text("E-Mail") }
)
OutlinedTextField(
value = password,
onValueChange = { newValue ->
password = newValue
},
label = { Text("Passwort") }
)
Button(
enabled = canSubmit,
onClick = { onLoginClick(email, password) }
) {
Text("Anmelden")
}
}
}
Hier sind email und password als var sinnvoll, weil sich die Eingaben bei jeder Änderung im Textfeld aktualisieren. Diese Veränderung ist echter Zustand. Compose muss wissen, dass sich dieser Zustand geändert hat, damit die UI neu gezeichnet werden kann. Deshalb wird mutableStateOf verwendet und mit remember an die Composition gebunden.
Die Werte isEmailValid, isPasswordValid und canSubmit sind dagegen val. Sie werden aus dem aktuellen Zustand berechnet. Du weist sie nicht später neu zu. Bei jeder Recomposition werden sie anhand der aktuellen Eingaben neu berechnet. Das ist ein wichtiger Unterschied: Ein val muss nicht für die gesamte Lebensdauer des Screens denselben Inhalt haben. Es gilt innerhalb des aktuellen Funktionsaufrufs. Wenn Compose die Funktion erneut ausführt, entstehen neue lokale Werte. Für den einzelnen Durchlauf bleiben sie aber stabil.
Eine typische Fehlentscheidung wäre, auch abgeleitete Werte als var zu speichern:
var canSubmit by remember { mutableStateOf(false) }
Dann müsstest du jedes Mal daran denken, canSubmit zu aktualisieren, wenn sich email oder password ändern. Vergisst du eine Stelle, zeigt die UI falsches Verhalten. Der Button bleibt vielleicht deaktiviert, obwohl die Eingaben gültig sind. Oder er wird aktiv, obwohl das Passwort zu kurz ist. Abgeleiteter Zustand sollte deshalb möglichst berechnet werden, statt separat gespeichert zu werden. Speichere nur den Zustand, der nicht aus anderen vorhandenen Werten ableitbar ist.
Diese Regel gilt nicht nur in Compose. In einem ViewModel sieht ein ähnlicher Stil oft so aus: Interner Zustand darf sich ändern, die öffentliche Oberfläche bleibt lesbar. Du kannst zum Beispiel intern eine MutableStateFlow halten und nach außen nur StateFlow freigeben. Der konkrete API-Typ ist ein späteres Roadmap-Thema, aber das Prinzip gehört direkt zu val und var: Änderungen werden an einer kontrollierten Stelle erlaubt, der Rest der App bekommt eine stabile, lesbare Sicht.
data class ProfileUiState(
val userName: String = "",
val isLoading: Boolean = false,
val errorMessage: String? = null
)
In dieser Datenklasse sind alle Properties val. Wenn sich der Zustand ändert, veränderst du nicht einzelne Felder im bestehenden Objekt. Du erzeugst einen neuen State, oft mit copy. Dadurch kannst du in Tests sehr gut prüfen, welcher Zustand vor und nach einer Aktion gilt. Außerdem wird Code-Review einfacher: Andere Entwickler sehen klar, welche Daten ein Screen besitzt und welche Änderungen explizit vorgenommen werden.
Ein häufiger Stolperstein ist die Annahme, var sei praktischer, weil man damit schneller etwas zum Laufen bekommt. Kurzfristig stimmt das manchmal. Du kannst einen Wert anlegen und später ändern, ohne über Datenfluss nachzudenken. Der Preis kommt später: Fehler werden schwerer reproduzierbar, weil eine Änderung aus einer entfernten Stelle stammen kann. Bei Android wird das durch Lebenszyklus und asynchrone Abläufe verstärkt. Eine Activity kann neu erstellt werden, eine Coroutine kann später zurückkehren, ein Flow kann neue Daten liefern, und Compose kann Funktionen erneut ausführen. Wenn du zu viel veränderlichen Zustand verstreust, wird die App schwer zu stabilisieren.
Eine zweite Falle ist unnötige Änderbarkeit in Parametern und Rückgabewerten. Wenn eine Funktion eine MutableList zurückgibt, erlaubst du dem Aufrufer, diese Liste zu verändern. Vielleicht war das nie beabsichtigt. Gib lieber List zurück, wenn der Aufrufer nur lesen soll. Genauso solltest du lokale Zwischenergebnisse als val deklarieren, damit beim Lesen klar ist: Ab hier bleibt dieser Name im aktuellen Ablauf gleich.
Praktische Entscheidungsregel: Frage bei jeder Deklaration kurz: „Muss dieser Name später im selben Scope neu zugewiesen werden?“ Wenn nein, nimm val. Wenn ja, frage weiter: „Ist diese Änderung echter Zustand oder nur eine bequeme Abkürzung?“ Echter Zustand sind zum Beispiel Nutzereingaben, Ladefortschritt oder eine Auswahl im UI. Eine bequeme Abkürzung ist oft ein Zeichen, dass du den Ausdruck anders strukturieren kannst, zum Beispiel mit map, filter, copy, einer Hilfsfunktion oder einem klareren Rückgabewert.
Für Übungen kannst du vorhandenen Code bewusst durchgehen. Markiere alle var-Stellen und begründe jede einzelne. Wenn du keine konkrete Änderung findest, ersetze sie durch val und lass den Compiler prüfen. Das ist eine sehr effektive Lerntechnik, weil Kotlin dir sofort zeigt, wo echte Neuzuweisung passiert. Ergänzend kannst du Tests schreiben, die UI-State oder Funktionsausgaben vergleichen. In Code-Reviews lohnt sich die Frage: „Kann das ein val sein?“ Sie wirkt klein, führt aber oft zu besserem Design.
Fazit
val und var sind kleine Schlüsselwörter mit großer Wirkung auf deinen Android-Code. Wenn du val als Standard nutzt, beschreibst du stabile Werte und reduzierst unnötige Nebenwirkungen. Wenn du var nutzt, sollte dahinter ein echter, benennbarer Zustand stehen, etwa eine Nutzereingabe oder ein kontrollierter UI-State in Compose. Prüfe dein Verständnis aktiv: Nimm eine bestehende Kotlin-Datei, ersetze geeignete var-Deklarationen durch val, beobachte Compilerfehler, gehe mit dem Debugger durch den Datenfluss und achte im Code-Review darauf, ob Änderbarkeit wirklich gebraucht wird.