State Hoisting: toestand omhoog tillen en unidirectionele gegevensstroom in Compose

Auteur: IT Sectr Gepubliceerd: 2026-06-28 Leestijd: 8 min

State Hoisting — is een patroon in Jetpack Compose waarbij de toestand wordt verplaatst van de onderliggende Composable-functie naar de bovenliggende, en de onderliggende ontvangt gegevens via parameters en meldt wijzigingen via callbacks. Dit is een implementatie van het principe van unidirectionele gegevensstroom (UDF), waarbij de toestand omhoog wordt getild en gebeurtenissen omlaag gaan. Volgens Google Android Developers, 2026 maakt State Hoisting componenten herbruikbaar, testbaar en voorspelbaar.

Belangrijkste punten

  • State Hoisting verplaatst toestand van onderliggende component naar bovenliggende
  • UDF (Unidirectional Data Flow) — toestand stroomt omlaag, gebeurtenissen omhoog
  • Parameters van onderliggende component: waarde (T) + lambda (T) -> Unit
  • Herbruikbaarheid — omhoog getilde toestand maakt het mogelijk dezelfde functie met verschillende bronnen te gebruiken
  • Testen — State Hoisting vereenvoudigt unittesten door logica van UI te isoleren

Wat is State Hoisting in Jetpack Compose

State Hoisting (toestand omhoog tillen) — is een patroon waarbij een Composable-functie geen toestand bezit, maar deze van buitenaf ontvangt. In plaats van var binnen de functie worden twee parameters gebruikt: de waarde voor weergave en een lambda-callback voor het afhandelen van wijzigingen. Technisch betekent dit dat de onderliggende component stateless wordt (geen eigen toestand) en de bovenliggende stateful (toestand bezit).

Voorbeeld: de TextField component uit Material3 slaat de ingevoerde tekst niet intern op. Het accepteert value: String en onValueChange: (String) -> Unit. De ouder die TextField aanroept, declareert var value by remember { mutableStateOf("") } en geeft value en onValueChange door. Dit is klassieke State Hoisting: TextField — een domme component (toont alleen en meldt invoer), de ouder — slim (bezit de toestand).

Stateless vs Stateful: Een Stateless component is gemakkelijker te testen — het hangt niet af van interne toestand, het gedrag wordt volledig bepaald door invoerparameters. Een Stateful component is handig voor snel prototypen, maar moeilijker te herbruiken — het is strak gekoppeld aan een enkele gegevensbron. State Hoisting biedt de keuze: elke component kan stateless worden gemaakt door de toestand omhoog te verplaatsen.

Unidirectionele gegevensstroom (UDF) en State Hoisting

UDF (Unidirectional Data Flow) — is een architectuurprincipe waarbij gegevens in één richting bewegen: van de bron van waarheid (ViewModel of bovenliggende Composable) naar de UI, en gebeurtenissen in de tegenovergestelde richting. State Hoisting is de implementatie van UDF op het niveau van individuele componenten. In plaats van dat elke component zelf beslist wanneer en hoe zijn toestand te wijzigen, meldt het de gebeurtenis aan de ouder, en de ouder beslist hoe de toestand te wijzigen.

Voordelen van UDF: voorspelbaarheid — toestand verandert slechts op één plaats, wat race conditions elimineert; traceerbaarheid — via de aanroepstack kan de keten van wijzigingen worden gereconstrueerd; testen — stateful logica kan in een aparte klasse worden geplaatst en zonder UI worden getest. In grote projecten is UDF in combinatie met State Hoisting de facto standaard.

Enkele bron van waarheid (Single Source of Truth) — nog een principe dat UDF vergezelt. Elk fragment van toestand heeft precies één bron. Als twee componenten dezelfde toestand gebruiken, moet de bron gedeeld zijn (op ViewModel-niveau of gemeenschappelijke ouder). State Hoisting garandeert dat de bron hoger in de hiërarchie staat en dat er geen duplicatie van toestand plaatsvindt.

RichtingWat wordt doorgegevenHoe geïmplementeerd
Omlaag (ouder → kind)Waarde voor weergaveParameter value: T
Omhoog (kind → ouder)WijzigingsgebeurtenisParameter onValueChange: (T) -> Unit

Regels van State Hoisting: wanneer en hoe toestand omhoog te tillen

Hoofdregel: toestand moet worden getild naar het minimaal mogelijke niveau dat voldoende is voor alle componenten die het nodig hebben. Als toestand slechts binnen één component wordt gebruikt — laat het lokaal. Als twee naburige componenten dezelfde toestand nodig hebben — til het naar de gemeenschappelijke ouder. Als toestand op het hele scherm nodig is — til het naar de ViewModel.

Regel van minimaal tillen voorkomt onnodige complexiteit. Het heeft geen zin om de toestand van een tekstveld naar ViewModel te tillen als het slechts binnen één scherm wordt gebruikt en niet wordt bewaard bij het opnieuw aanmaken van Activity. Gebruik rememberSaveable op het niveau van de scouder, niet ViewModel, voor UI-toestand die een schermrotatie moet overleven maar niet nodig is voor bedrijfslogica.

Wanneer tillen naar ViewModel: als toestand moet worden bewaard bij het opnieuw aanmaken van Activity, als het nodig is voor meerdere schermen, als een toestandsverandering bedrijfslogica activeert (netwerkverzoeken, database). State Hoisting op ViewModel-niveau is een typisch patroon in MVVM-architectuur, waar de UI-laag stateless is en ViewModel stateful.

kotlin
    // ❌ Slecht: component bezit eigen toestand
@Composable
fun BadTextField(label: String) {
    var text by remember { mutableStateOf("") }
    TextField(value = text, onValueChange = { text = it }, label = { Text(label) })
}

    // ✅ Goed: State Hoisting — toestand in ouder
@Composable
fun GoodTextField(value: String, onValueChange: (String) -> Unit, label: String) {
    TextField(value = value, onValueChange = onValueChange, label = { Text(label) })
}

    // Gebruik: ouder bezit de toestand
@Composable
fun Form() {
    var name by rememberSaveable { mutableStateOf("") }
    GoodTextField(value = name, onValueChange = { name = it }, label = "Name")
}

Voorbeelden van State Hoisting in echte componenten

Beschouw het login scherm met twee velden (email, wachtwoord) en een knop. Alle drie componenten ontvangen toestand via State Hoisting: email en wachtwoord worden beheerd door de ouder, de knop ontvangt de enabled-status als waarde.

kotlin
    // State Hoisting op schermniveau
@Composable
fun LoginScreen(viewModel: LoginViewModel) {
    val uiState by viewModel.uiState.collectAsState()

    Column(modifier = Modifier.padding(16.dp)) {
        // Email-veld — State Hoisting via lambda
        EmailField(
            email = uiState.email,
            onEmailChange = { viewModel.onEmailChanged(it) }
        )

        // Wachtwoordveld — idem
        PasswordField(
            password = uiState.password,
            onPasswordChange = { viewModel.onPasswordChanged(it) }
        )

        // Knop — ontvangt alleen enabled (read-only)
        LoginButton(enabled = uiState.isFormValid, onClick = viewModel::login)
    }
}

// Stateless component: ontvangt email + callback
@Composable
fun EmailField(email: String, onEmailChange: (String) -> Unit) {
    OutlinedTextField(
        value = email,
        onValueChange = onEmailChange,
        label = { Text("Email") },
        singleLine = true
    )
}

// Stateless knopcomponent
@Composable
fun LoginButton(enabled: Boolean, onClick: () -> Unit) {
    Button(onClick = onClick, enabled = enabled) {
        Text("Inloggen")
    }
}

EmailField en PasswordField — volledig stateless. Ze kunnen op elk scherm worden herbruikt, gekoppeld aan elke gegevensbron. LoginButton ontvangt enabled als read-only — dit is een andere vorm van State Hoisting waarbij toestand niet wordt getild (de knop kan zichzelf niet inschakelen), maar kant-en-klaar wordt doorgegeven. Deze benadering biedt maximale flexibiliteit met minimale componentkoppeling.

State Hoisting vs lokale toestand: selectiecriteria

Niet elke toestand hoeft te worden getild. Lokale toestand (State binnen Composable) is gerechtvaardigd wanneer: gegevens slechts binnen één component nodig zijn, ze naburige elementen niet beïnvloeden, ze de recompositie van een specifieke sectie niet hoeven te overleven. Bijvoorbeeld, animatietoestand, focus van een invoerveld, huidige scrollpositie — het is redelijk om lokaal te houden.

Wanneer State Hoisting noodzakelijk is: toestand wordt gebruikt door meerdere onderliggende componenten; een wijziging in één kind moet worden weerspiegeld in een ander; de logica van toestandsverandering moet apart van UI worden getest; toestand moet worden bewaard bij het opnieuw aanmaken van Activity. In deze gevallen creëert lokale toestand duplicatie en inconsistentie van gegevens.

Hybride benadering: houd minimale toestand lokaal, til de rest. De regel van Compose: “til toestand zo hoog als nodig en zo laag als mogelijk”. In de praktijk betekent dit beginnen met lokale remember, en pas wanneer er behoefte ontstaat aan toegang vanuit een andere component — het niveau verhogen. Doe State Hoisting niet preventief — het compliceert de code onnodig.

Veelgestelde vragen

Wat is het verschil tussen State Hoisting en ViewModel?

State Hoisting — is een patroon op het niveau van UI-componenten. ViewModel — is een architectuurlaag voor bedrijfslogica. State Hoisting kan toestand tillen naar het niveau van de bovenliggende Composable, naar het schermniveau of naar ViewModel. ViewModel — is het hoogste punt van tillen voor toestand die het opnieuw aanmaken van Activity moet overleven.

Hoe test je een component met State Hoisting?

Een stateless component wordt getest door simpelweg waarden door te geven. Roep de Composable aan met de juiste parameters en controleer de weergave via ComposeTestRule. Toestandsverandering wordt getest op het niveau van de ouder of ViewModel — apart van de UI. Dit vereenvoudigt tests aanzienlijk: er hoeft geen recompositie binnen de component te worden gesimuleerd.

Kan State alleen-lezen worden getild?

Ja, dit is gangbare praktijk. Als een component alleen gegevens moet weergeven zonder ze te kunnen wijzigen — geef State<T> (read-only) door. De component zal geabonneerd zijn op wijzigingen, maar kan ze niet initiëren. Dit versterkt de inkapseling en beschermt gegevens tegen ongewenste mutaties.

Wat als toestand 3+ niveaus diep moet worden getild?

Voor diepe overdracht gebruik CompositionLocal of overdracht via parameters van de bovenliggende Composable. Als toestand op het hele scherm nodig is — verplaats het naar ViewModel en gebruik collectAsState(). Doorgeven via 5+ niveaus is een teken van verkeerde architectuur; heroverweeg de componentenhierarchie.

Beïnvloedt State Hoisting de prestaties?

State Hoisting kan het aantal recomposities licht verhogen, omdat een wijziging in de ouder alle kinderen kan recomponeren. Gebruik derivedStateOf voor het filteren van wijzigingen en keys in LazyColumn voor gerichte updates. In de meeste scenario's is de overhead van State Hoisting verwaarloosbaar in vergelijking met de voordelen van onderhoudbaarheid.

Samenvatting

  • State Hoisting verplaatst toestand naar de bovenliggende component, waardoor het kind stateless wordt
  • UDF garandeert unidirectionele gegevensstroom: toestand omlaag, gebeurtenissen omhoog
  • Herbruikbaarheid — stateless componenten kunnen aan elke gegevensbron worden gekoppeld
  • Testen — UI-tests controleren alleen weergave, logica wordt apart getest
  • Minimaal tillen — til precies zo ver als nodig
  • ViewModel — hoogste punt van tillen voor toestand met bedrijfslogica
  • Aanbeveling: begin met lokale remember, til alleen wanneer nodig

We ontwikkelen een mobiele applicatie turnkey

IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.

Bespreek het project

Lees ook