MutableState — waarneembare toestand en updatemechanisme in Compose

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

MutableState — is een interface in Jetpack Compose die een container voor een veranderlijke waarneembare waarde vertegenwoordigt. Het vormt de basis van het reactieve systeem van Compose: telkens wanneer de waarde van MutableState verandert via de setter, stelt Compose Runtime alle lezende componenten op de hoogte en start recompositie. Volgens Google Android Developers, 2026 is begrip van MutableState verplicht voor correct werken met toestand in declaratieve UI.

Belangrijkste punten

  • MutableState — compose.runtime-interface met één eigenschap value (getter + setter)
  • State — bovenliggende alleen-lezen interface, MutableState voegt schrijfmogelijkheid toe
  • Recompositie wordt gestart bij aanroep van de value-setter binnen een actieve snapshot-cyclus
  • SnapshotMutationPolicy bepaalt wanneer een verandering als significant voor recompositie wordt beschouwd
  • MutableIntState en dergelijke — geoptimaliseerde primitieve versies van MutableState

Wat is MutableState in Jetpack Compose

MutableState — is een interface uit het pakket androidx.compose.runtime die één eigenschap declareert: override var value: T. De getter retourneert de huidige waarde, de setter schrijft een nieuwe en stelt Compose Runtime op de hoogte van de wijziging. De interface erft van State<T>, waarbij value alleen-lezen is. Deze tweelaagse architectuur maakt scheiding van toegang mogelijk: een component dat alleen de waarde hoeft te lezen krijgt State<T>, en de eigenaarcomponent — MutableState<T>.

De standaardimplementatie van MutableState is de interne klasse SnapshotMutableStateImpl, die het snapshot-mechanisme gebruikt voor het bijhouden van wijzigingen. Wanneer de value-setter wordt aangeroepen, registreert de huidige snapshot de schrijfactie en markeert alle geregistreerde ObservedScope-gebieden (observatiegebieden) als ongeldig. Deze gebieden (meestal Composable-functies) worden in het volgende frame gerecomposeerd. Het hele proces verloopt synchroon en zonder blokkades dankzij de Lock-free snapshot-architectuur.

State vs MutableState: State — is een alleen-lezen interface, gebruikt voor publieke API's van componenten. Wanneer u een parameter van een Composable-functie declareert als State<Int>, garandeert u dat het component kan lezen maar de toestand niet kan wijzigen. MutableState wordt gebruikt binnen de eigenaarcomponent. Een dergelijke scheiding — een van de basispraktijken van Compose — voorkomt ongeautoriseerde wijzigingen.

Hiërarchie van State, MutableState en afgeleide interfaces

De hiërarchie van toestandsinterfaces in Compose heeft meerdere niveaus. Bovenaan — State<T> met alleen-lezen value. Daaronder — MutableState<T> met read-write value. Daarna komen gespecialiseerde primitieve versies: MutableIntState, MutableFloatState, MutableLongState, MutableBooleanState en andere, die auto-boxing van primitieven vermijden.

MutableDoubleState en MutableLongState — minder voorkomende maar bestaande typen. Collectie-interfaces: MutableListState — voor het bijhouden van wijzigingen in een lijst, MutableStateMap — voor maps. Elk van deze interfaces is geoptimaliseerd voor een specifiek scenario en breidt de basis-MutableState uit met extra methoden voor collectiebewerking.

SnapshotStateList en SnapshotStateMap — zijn implementaties van veranderlijke lijsten en maps, compatibel met snapshots. Ze maken het mogelijk niet alleen vervanging van de waarde te volgen, maar ook interne wijzigingen: toevoegen van een element aan een lijst, verwijderen, wijzigen van een bestaand element. Voor dergelijke structuren maken mutableStateListOf() en mutableStateMapOf() de corresponderende waarneembare collecties.

InterfaceDoelAanmaakmethode
State<T>Alleen-lezen container
MutableState<T>Lees-schrijf containermutableStateOf()
MutableIntStatePrimitieve Int zonder boxingmutableIntStateOf()
MutableFloatStatePrimitieve Float zonder boxingmutableFloatStateOf()
SnapshotStateListWaarneembare lijstmutableStateListOf()
SnapshotStateMapWaarneembare mapmutableStateMapOf()

SnapshotMutationPolicy: wanneer recompositie starten

SnapshotMutationPolicy — is een interface die bepaalt wanneer een wijziging van MutableState als significant wordt beschouwd. mutableStateOf accepteert policy als tweede argument. Standaardimplementaties: structuralEquality() (equals), referentialEquality() (===), neverEqualPolicy() (beschouwt wijziging altijd als significant). Voor eigen logica kunt u uw eigen policy implementeren.

structuralEquality() — standaardgedrag. Compose vergelijkt de nieuwe waarde met de oude via equals(). Als het resultaat true is — wordt recompositie NIET gestart. Dit is handig voor primitieven en data classes, waar twee instanties met dezelfde velden als gelijk worden beschouwd. Probleem: als een data class een List bevat, voert equals() een diepe vergelijking uit, wat kostbaar kan zijn voor grote lijsten.

referentialEquality() — vergelijkt referenties via ===. Recompositie wordt alleen gestart bij toewijzing van een ander object, zelfs als de inhoud identiek is. Dit is optimaal voor onveranderlijke data classes, waar elke nieuwe instantie gegarandeerd een wijziging betekent. neverEqualPolicy() — beschouwt wijziging altijd als significant zonder vergelijking uit te voeren. Nuttig wanneer de setter zelden wordt aangeroepen en er geen tijd aan equals hoeft te worden besteed.

kotlin
    // Beleidsvergelijking in de praktijk
data class User(val name: String, val age: Int)

@Composable
fun UserProfile() {
    // structuralEquality: recompositie ALLEEN als gegevens zijn gewijzigd
    var user1 by remember {
        mutableStateOf(User("Alice", 30))
    }

    // referentialEquality: recompositie bij ELKE toewijzing
    var user2 by remember {
        mutableStateOf(User("Bob", 25),
            SnapshotMutationPolicy.referentialEquality())
    }

    // user1: copy() met dezelfde velden veroorzaakt GEEN recompositie
    // user2: zelfs user2.copy() == user2 veroorzaakt recompositie (nieuwe ref)
}

Primitieve State: MutableIntState, MutableFloatState, MutableLongState

Primitieve MutableIntState en dergelijke — zijn gespecialiseerde interfaces die primitieven opslaan zonder auto-boxing. Gewone MutableState<Int> slaat Int op als Integer, wat bij elke schrijfactie een object op de heap creëert. MutableIntState slaat int (primitief) op, waardoor de overhead van boxing volledig wordt geëlimineerd. Dit is vooral belangrijk bij hoogfrequente updates — tellers, scrollposities, animatiewaarden.

mutableIntStateOf(), mutableFloatStateOf(), mutableLongStateOf() — functies die primitieve MutableState creëren. De interfaces heten MutableIntState, MutableFloatState, MutableLongState. Ze breiden respectievelijk MutableState<Int>, MutableState<Float> en MutableState<Long> uit, met toevoeging van de eigenschap intValue als snelle toegang tot de primitief. In hun interne implementatie wordt AtomicInteger gebruikt voor lock-free lezen/schrijven.

Toepassing: tellers (Int), scrollposities (Float offset), tijdstempels (Long). In de meeste dagelijkse scenario's is het prestatieverschil niet merkbaar, maar in LazyList met duizenden elementen en overgangsanimaties geven primitieve State een merkbare verbetering. Google raadt aan primitieve State te gebruiken voor typische scenario's in plaats van universele mutableStateOf.

kotlin
@Composable
fun ScrollCounter() {
    // Slecht: bij elke update inpakken
    var badCount by remember { mutableStateOf(0) }

    // Goed: geen inpakken, primitieve opslag
    var goodCount by remember { mutableIntStateOf(0) }

    // Gebruik is identiek
    Button(onClick = { goodCount++ }) {
        Text("Count: $goodCount")
    }
}

Praktische voorbeelden van werken met MutableState

Laten we het component TodoList bekijken, waar MutableState in twee vormen wordt gebruikt: als afzonderlijke variabelen voor invoertoestand en als SnapshotStateList voor een dynamische takenlijst. Beide gebruiken delegatie voor beknoptheid van code.

kotlin
data class TodoItem(val id: Int, val text: String, val isDone: Boolean = false)

@Composable
fun TodoScreen() {
    var inputText by remember { mutableStateOf("") }
    val items = remember { mutableStateListOf() }

    Column(modifier = Modifier.padding(16.dp)) {
        Row {
            TextField(
                value = inputText,
                onValueChange = { inputText = it }
            )
            Button(onClick = {
                if (inputText.isNotBlank()) {
                    items.add(TodoItem(items.size, inputText))
                    inputText = ""
                }
            }) { Text("Toevoegen") }
        }

        LazyColumn {
            items(items) { item ->
                Row(modifier = Modifier.fillMaxWidth().clickable {
                    val idx = items.indexOf(item)
                    items[idx] = item.copy(isDone = !item.isDone)
                }) {
                    Checkbox(checked = item.isDone, onCheckedChange = null)
                    Text(item.text)
                }
            }
        }
    }
}

mutableStateListOf creëert een SnapshotStateList — een veranderlijke lijst die wijzigingen van afzonderlijke elementen bijhoudt. Bij aanroep van items.add() en items[n] = newValue ziet Compose de mutatie en recomposeert alleen die LazyColumn-elementen die zijn gewijzigd. inputText — een gewone MutableState<String>. Combinatie van twee typen MutableState (enkelvoudig en collectie) — een typisch patroon voor schermen met formulieren en lijsten.

Veelgestelde vragen

Kan MutableState zonder remember worden gebruikt?

MutableState zonder remember wordt bij elke recompositie opnieuw aangemaakt. Elke nieuwe aanroep van mutableStateOf creëert een nieuw object en de oude waarde gaat verloren. Gebruik altijd remember om State tussen recomposities te behouden, tenzij State buiten Composable wordt gecreëerd (bijvoorbeeld in ViewModel).

Hoe converteer ik MutableState naar een gewone variabele?

Lees .value eenmalig buiten de snapshot via snapshot { }. Maar dit schakelt reactiviteit uit — wijzigingen zullen geen recompositie meer veroorzaken. Voor eenmalig lezen zonder abonnement gebruikt u currentValue() binnen de snapshot zonder lezen.

Wat is sneller: mutableStateOf of mutableIntStateOf?

mutableIntStateOf is sneller omdat het geen inpakken van int in Integer vereist. Bij duizenden updates per seconde (animatie, scroll) kan het verschil 30-50% van de allocatietijd bedragen. Bij zeldzame updates (klikken, tekstinvoer) is het verschil verwaarloosbaar.

Kan MutableState worden gebruikt als argument van een Composable-functie?

Het kan, maar wordt niet aanbevolen. Geef in plaats van MutableState State (alleen-lezen) + lambda onValueChange door. Dit implementeert het State Hoisting-patroon en maakt het component herbruikbaar. Componenten die MutableState accepteren schenden de unidirectionele gegevensstroom.

Hoe maak ik een aangepaste implementatie van MutableState?

Implementeer de MutableState-interface en zorg voor override var value met getter en setter. In de setter kunt u validatie of logging toevoegen. Voor achterwaartse compatibiliteit met Compose Runtime wikkelt u uw implementatie in snapshotFlow of gebruikt u snapshotIncrement.

Samenvatting

  • MutableState — basisinterface voor veranderlijke waarneembare toestand in Compose
  • State — alleen-lezen versie voor gegevensoverdracht zonder wijzigingsrecht
  • SnapshotMutationPolicy beheert de voorwaarden voor het starten van recompositie bij wijziging
  • Primitieve State (MutableIntState enz.) elimineren overhead van auto-boxing
  • SnapshotStateList en SnapshotStateMap volgen interne wijzigingen van collecties
  • Recompositie wordt automatisch gestart bij elke aanroep van de value-setter
  • Aanbeveling: gebruik MutableState voor het bezitten van toestand en State voor doorgeven naar beneden

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