Android Coden
Android 5 min lesen

State-Holder-Alternativen in Compose

Nicht jeder Zustand gehört ins ViewModel. Du lernst, wann remember, ein plain State Holder oder ViewModel die richtige Wahl ist.

In Jetpack Compose stellt sich früh die Frage: Wo lebt eigentlich der UI-Zustand meiner App? Viele Einsteiger greifen reflexartig zum ViewModel, weil es vertraut wirkt. Doch die Wahl des richtigen State Holders ist eine echte Architekturentscheidung — und das offizielle Android-Architektur-Leitbild benennt drei klar unterschiedliche Optionen: remember, plain State Holder und ViewModel. Wer alle drei kennt und sauber einsetzt, schreibt lesbaren, testbaren und performanten Code.

Was ist das?

State Holder Alternativen beschreiben die verschiedenen Orte, an denen du UI-Zustand in einer Compose-Anwendung ablegen kannst. Jede Option hat eine klar definierte Rolle:

remember hält Zustand direkt in einer Composable-Funktion. Der Zustand existiert so lange, wie die Composable in der Composition verbleibt. Verlässt sie die Composition — weil ein Tab wechselt oder ein Screen aus dem Backstack entfernt wird — geht der Zustand verloren. Das ist oft genau richtig für kurzlebige, rein visuelle Zustände.

Plain State Holder ist eine gewöhnliche Kotlin-Klasse, die zusammengehörige Zustände und Logik kapselt. Sie hat keine Android-Framework-Abhängigkeiten und wird typischerweise mit remember { MyStateHolder() } in die Composition eingebunden. Sie verlängert die Lebensdauer des Zustands nicht automatisch über Konfigurationsänderungen hinaus.

ViewModel ist ein Jetpack-Bestandteil, der Zustand über Konfigurationsänderungen wie Gerätedrehungen hinweg erhält. Er entkoppelt UI-Logik von der Composable und fungiert als Brücke zur Domain- und Datenschicht der App.

Die Android-Architektur-Dokumentation betont ausdrücklich, dass es keine universell richtige Wahl gibt. Die Entscheidung hängt von drei Faktoren ab: Lebensdauer, Besitz und Testbarkeit.

Wie funktioniert es?

Lifecycle: Wie lange muss der Zustand leben?

remember bindet den Zustand an den Composition-Lebenszyklus der Composable. Ein ausgeklapptes Dropdown, ein Hover-Effekt oder ein temporärer Eingabepuffer — all das gehört hierher. Kein Overhead, keine Abhängigkeit, keine Konfigurationsrettung nötig.

Ein ViewModel überlebt Konfigurationsänderungen, weil es vom ViewModelStore des Activity- oder Fragment-Scope gehalten wird. Zustand, der diese Grenze überschreiten muss — ein ausgefülltes Formular, Suchergebnisse, Authentifizierungsstatus — gehört ins ViewModel.

Ein plain State Holder lebt so lange wie das remember, das ihn hält. Er hat keinen eigenen Lifecycle-Mechanismus, eignet sich aber hervorragend, wenn eine Composable komplex genug wird, um Logik auszulagern, ohne den Zustand über Konfigurationsänderungen hinweg halten zu müssen.

Besitz: Wer darf diesen Zustand kennen?

Sobald echter Datenzugriff ins Spiel kommt — Netzwerk, Datenbank, SharedPreferences — ist das ViewModel die einzig sinnvolle Wahl. Es ist der einzige State Holder in der UI-Schicht, der mit Repositories und Use Cases kommuniziert. Ein plain State Holder kennt keine anderen Schichten der App. Er ist rein für UI-interne Logik gedacht: Validierungsregeln, Eingabeformatierung, Animationszustände.

Testbarkeit: Wie einfach lässt sich der Zustand prüfen?

Plain State Holder sind einfache Kotlin-Klassen ohne Android-Abhängigkeiten. Du kannst sie in einem normalen JVM-Test direkt instanziieren und alle Methoden aufrufen — kein Emulator, kein Robolectric, kein aufwändiges Setup. ViewModels benötigen mehr Vorbereitung, sind aber mit TestCoroutineDispatcher und der Turbine-Bibliothek für Flows gut testbar. Zustand, der nur in remember lebt, ist ausschließlich über Compose-UI-Tests zugänglich, was die Testpyramide verzerrt und Testlaufzeiten erhöht.

In der Praxis

Stell dir ein Registrierungsformular vor. Es enthält Name und E-Mail, eine Validierungslogik und einen Absende-Button. Die Logik wächst schnell über das hinaus, was sinnvoll direkt in der Composable steht — aber ein Netzwerkaufruf findet hier noch nicht statt. Ein plain State Holder ist die passende Lösung:

class RegistrationFormState(
    initialName: String = "",
    initialEmail: String = ""
) {
    var name by mutableStateOf(initialName)
        private set
    var email by mutableStateOf(initialEmail)
        private set

    val isNameValid: Boolean get() = name.isNotBlank()
    val isEmailValid: Boolean get() = email.contains("@") && email.contains(".")
    val isFormValid: Boolean get() = isNameValid && isEmailValid

    fun updateName(value: String) { name = value }
    fun updateEmail(value: String) { email = value }
}

@Composable
fun RegistrationForm() {
    val formState = remember { RegistrationFormState() }

    Column(modifier = Modifier.padding(16.dp)) {
        TextField(
            value = formState.name,
            onValueChange = { formState.updateName(it) },
            label = { Text("Name") },
            isError = !formState.isNameValid && formState.name.isNotEmpty()
        )
        Spacer(modifier = Modifier.height(8.dp))
        TextField(
            value = formState.email,
            onValueChange = { formState.updateEmail(it) },
            label = { Text("E-Mail") },
            isError = !formState.isEmailValid && formState.email.isNotEmpty()
        )
        Spacer(modifier = Modifier.height(16.dp))
        Button(
            onClick = { /* weiterleiten ans ViewModel */ },
            enabled = formState.isFormValid
        ) {
            Text("Registrieren")
        }
    }
}

Diesen RegistrationFormState kannst du ohne Compose-Test direkt prüfen:

@Test
fun `isFormValid ist false bei leerem Namen`() {
    val state = RegistrationFormState(initialEmail = "test@example.com")
    assertFalse(state.isFormValid)
}

Typische Stolperfalle: Viele Entwicklerinnen und Entwickler packen solche Formular-Logik ins ViewModel, weil es „sicher” wirkt. Das Ergebnis sind aufgeblähte ViewModels mit Dutzenden von Feldern, die nur das lokale UI betreffen und keinerlei Datenzugriff haben. Das erschwert Lesbarkeit und Tests zugleich. Drehe die Frage um: Muss dieser Zustand eine Konfigurationsänderung überleben oder eine andere Schicht der App kennen? Falls beides nein — dann reicht remember oder ein plain State Holder vollständig aus.

Kurzformel für die tägliche Entscheidung:

  • Zustand lebt nur in einer Composable, kurzlebig → remember
  • Logik wächst, aber kein Datenzugriff nötig → plain State Holder mit remember
  • Datenzugriff, Konfigurationsrettung oder Kommunikation mit anderen Schichten → ViewModel

Fazit

Die Wahl des richtigen State Holders gehört zu den unterschätzten Architekturentscheidungen in Compose-Projekten. Sie beeinflusst Lesbarkeit, Testabdeckung und Performance gleichermaßen. Geh deinen aktuellen Code durch: Gibt es ViewModels, die ausschließlich lokalen UI-Zustand verwalten, ohne einen einzigen Repository-Aufruf? Lagere diesen Zustand in einen plain State Holder aus und schreib einen einfachen JVM-Test dafür. Du wirst sofort merken, wie viel übersichtlicher der ViewModel wird — und wie leicht sich der isolierte State Holder testen lässt.

Quellen (6)
Redaktion

Geschrieben von

Redaktion

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