setState() — de belangrijkste methode van State in Flutter, die het framework op de hoogte stelt van gegevenswijziging en de herbouw van de interface start. Volgens de officiële Flutter-documentatie (Flutter.dev, 2026) is setState het belangrijkste mechanisme van reactiviteit in StatefulWidget: zonder aanroep ervan zal de UI niets weten over wijzigingen in de State-velden en in de vorige toestand blijven. De methode accepteert een VoidCallback, waarbinnen de ontwikkelaar de veranderlijke velden wijzigt, waarna Flutter automatisch build aanroept om de widget te herbouwen.
Belangrijkste punten
setState() — een ingebouwde methode van de klasse State in Flutter, bedoeld om het framework te melden dat de interne toestand van de widget is gewijzigd en herbouw van de UI nodig is. Zonder setState-aanroep weet Flutter niets van wijzigingen — zelfs als State-velden zijn gewijzigd, blijft de interface ongewijzigd tot de volgende gedwongen herbouw door de ouder.
Handtekening van de methode: void setState(VoidCallback fn). De callback wordt synchroon uitgevoerd binnen setState en pas na voltooiing wordt State als dirty gemarkeerd. Dit garandeert dat alle wijzigingen atomair worden toegepast vóór herbouw. Volgens de Dart Language Specification (Dart Team, 2026) voorkomt de atomiciteit van setState race-condities, waarbij build een gedeeltelijk bijgewerkte toestand zou kunnen zien.
setState accepteert geen argumenten, retourneert geen waarde en kan niet worden overschreven. Het is een finale (sealed) methode van de klasse State. De ontwikkelaar kan het gedrag niet wijzigen — het kan alleen volgens bestemming worden gebruikt. Een poging om setState buiten State aan te roepen (bijvoorbeeld vanuit een andere klasse) is onmogelijk, omdat de methode in de klasse State is gedeclareerd.
Een veelvoorkomende misvatting — denken dat setState zelf de toestand wijzigt. Dit is niet waar. setState roept alleen de doorgegeven callback aan (waarin de ontwikkelaar de velden wijzigt) en signaleert vervolgens het framework over de noodzaak van build. De callback is verplicht — het doorgeven van null of een lege callback veroorzaakt een fout.
Het werkingsmechanisme van setState() kan in vier fasen worden verdeeld. Eerste — aanroep van de methode met callback. Tweede — synchrone uitvoering van de callback, waarbinnen de State-velden worden gewijzigd. Derde — State wordt gemarkeerd als dirty in het speciale veld _dirty. Vierde — aan het einde van de huidige microtask doorloopt Flutter alle dirty-elementen en roept hun build aan in volgorde van voorkomen in de boom.
Een belangrijk detail: setState roept build niet onmiddellijk aan. Flutter gebruikt een batch-updatestrategie: alle dirty-elementen worden verzameld en in één frame herbouwd. Dit betekent dat als setState meerdere keren binnen hetzelfde synchrone blok wordt aangeroepen, build slechts één keer wordt uitgevoerd — na voltooiing van alle wijzigingen. Deze optimalisatie voorkomt meerdere herbouwen per frame.
Volgens het Flutter Engine Team (Google, 2025) is het mechanisme van dirty-vlaggen gebaseerd op het doorlopen van BuildOwner._dirtyElements. Elk dirty StatefulElement wordt aan de lijst toegevoegd en verwerkt in de frame-updatefase. Als de widget vóór verwerking uit de boom is verwijderd, wordt deze automatisch uit de lijst van dirty-elementen uitgesloten.
Basisvoorbeeld van setState() met teller-increment. Demonstreert correct gebruik: veld wijzigen binnen de callback:
class _CounterState extends State<CounterWidget> {
int _count = 0;
void _increment() {
setState(() {
_count++; // veld muteren binnen callback
});
}
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: _increment,
child: Text('$_count'),
);
}
}
Voorbeeld met tekstveld en controller — setState() voor het beheren van wachtwoordzichtbaarheid:
class _PasswordFieldState extends State<PasswordField> {
bool _obscured = true;
final _controller = TextEditingController();
void _toggleVisibility() {
setState(() {
_obscured = !_obscured;
});
}
@override
Widget build(BuildContext context) {
return TextField(
controller: _controller,
obscureText: _obscured,
decoration: InputDecoration(
suffixIcon: IconButton(
icon: Icon(_obscured ? Icons.visibility : Icons.visibility_off),
onPressed: _toggleVisibility,
),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
In dit voorbeeld wijzigt setState() alleen het booleaanse veld _obscured, wat herbouw van TextField met een nieuw pictogram en weergavemodus veroorzaakt. De tekstcontroller wordt niet opnieuw aangemaakt — deze wordt eenmaal geïnitialiseerd in initState en vrijgegeven in dispose.
Als meerdere velden moeten worden gewijzigd, worden alle wijzigingen binnen één setState uitgevoerd. Dit garandeert dat build een consistente toestand ziet:
setState(() {
_isLoading = false;
_items = newItems;
_error = null;
});
Drie velden worden in één callback gewijzigd — build wordt één keer uitgevoerd en ziet alle wijzigingen tegelijk. Als elke aanroep een afzonderlijke setState was geweest, zou build nog steeds eenmalig worden uitgevoerd dankzij batchverwerking van dirty-elementen.
Een van de belangrijkste nuances van setState() — het gedrag met asynchrone bewerkingen. De setState-callback wordt synchroon uitgevoerd, maar als er binnenin await wordt aangeroepen, wordt de code na await uitgevoerd nadat setState zijn werk heeft voltooid. Dit betekent dat wijzigingen van velden na await niet worden opgevangen door de huidige setState.
Juiste benadering: de asynchrone bewerking wordt buiten setState uitgevoerd en setState wordt na voltooiing aangeroepen. Alle code tussen het ontvangen van het resultaat en het aanroepen van setState wordt in synchrone context na await uitgevoerd:
// CORRECT: await buiten setState
Future<void> _loadData() async {
final result = await ApiService.fetchData();
setState(() {
_data = result;
_isLoading = false;
});
}
// FOUT: await binnen setState — geen updategarantie
void _loadDataWrong() {
setState(() async {
_data = await ApiService.fetchData(); // setState keert terug vóór await voltooid is
_isLoading = false; // deze code wordt niet opgevangen door setState
});
}
Volgens Flutter-documentatie (Dart async patterns, 2026) is het doorgeven van een async-callback aan setState een antipatroon, omdat setState een VoidCallback (synchrone functie) verwacht, terwijl een async-functie een Future retourneert die wordt genegeerd. Wijzigingen na de eerste await in een dergelijke callback worden niet correct verwerkt door het framework.
Controleer vóór het aanroepen van setState() na een asynchrone bewerking altijd mounted:
if (mounted) {
setState(() => _data = data);
}
Als de widget tijdens de uitvoering van de asynchrone bewerking uit de boom is verwijderd, wordt mounted false en wordt setState niet aangeroepen. Dit voorkomt uitzonderingen en geheugenlekken.
setState() — een handig maar potentieel duur mechanisme, als het gedachteloos wordt gebruikt. Elke setState-aanroep herbouwt de hele widget en al zijn afstammelingen (als ze niet const zijn). In diepe bomen of bij frequente aanroepen kan dit leiden tot FPS-dalingen.
Belangrijkste optimalisatiestrategieën: minimaliseer het herbouwgebied (haal variabele UI-delen naar aparte StatefulWidgets), gebruik const voor onveranderlijke afstammelingen en vermijd setState-aanroepen in ouder-widgets als slechts een klein UX-detail is gewijzigd. Als de toestand met hoge frequentie wordt bijgewerkt (animatie, gegevensstroom), overweeg dan AnimatedBuilder of ValueListenableBuilder.
Volgens Flutter Performance Best Practices (Flutter.dev, februari 2026) toont profilering van echte applicaties aan dat tot 40% van alle setState-aanroepen kan worden vervangen door const-kind-widgets of reactieve builders (StreamBuilder, FutureBuilder). Dit vermindert de gemiddelde framebouwtijd met 15–25%.
| Scenario | Alternatief | Voordeel |
|---|---|---|
| Animatie | AnimatedBuilder | Herbouwt alleen de geanimeerde widget |
| Gegevensstroom | StreamBuilder | Reageert op elk element van de stroom |
| Toekomstig resultaat | FutureBuilder | Beheert laad-/fouttoestanden |
| Lokale waarde | ValueListenableBuilder | Reageert op wijzigingen van één waarde |
Ondanks de veelzijdigheid van setState() wordt het in grote projecten voornamelijk gebruikt voor lokale toestand. Voor globale of gedeelde toestand worden gespecialiseerde oplossingen gebruikt, die elk setState vervangen of inpakken.
Provider gebruikt ChangeNotifier + notifyListeners als analoog van setState, maar met de mogelijkheid van abonnement door meerdere widgets. Bloc gebruikt Streams — de toestand verandert door gebeurtenissen aan StreamController toe te voegen. Riverpod combineert benaderingen en biedt zowel lokaal (StateProvider) als asynchroon (AsyncNotifier) beheer zonder binding aan StatefulWidget. Alle drie benaderingen elimineren de noodzaak om handmatig setState aan te roepen — UI-update gebeurt automatisch bij gegevenswijziging.
Volgens Flutter Community Survey 2025 (Flutter Foundation, december 2025) gebruikt 74% van de ontwikkelaars ten minste één hulpmiddel voor toestandsbeheer naast setState. Tegelijkertijd blijft 92% setState gebruiken voor lokale gegevens van tekstveld, checkbox of eenvoudige teller — dit wordt beschouwd als best practice.
De eerste en gevaarlijkste fout — setState aanroepen na dispose. Een asynchrone bewerking is gestart in initState, de gebruiker heeft het scherm verlaten, de widget is verwijderd en de callback van de asynchrone bewerking roept setState aan — de applicatie crasht met een uitzondering. Oplossing — controleer altijd mounted vóór aanroep.
Tweede fout — setState aanroepen binnen build. Dit leidt tot een oneindige lus: build → setState → dirty → build → setState → ... Flutter blokkeert een dergelijke aanroep niet (u krijgt StackOverflowError). setState mag alleen worden aangeroepen als reactie op een gebeurtenis (knopdruk, voltooiing van Future, ontvangst van gegevens uit een stroom).
Derde fout — State-velden wijzigen zonder setState aan te roepen. De ontwikkelaar schrijft _count++ en verwacht dat de UI wordt bijgewerkt. Flutter kan veldwijzigingen niet automatisch volgen — het heeft een expliciet signaal via setState nodig. Dit is een fundamenteel verschil met reactieve frameworks zoals Vue.js, waar gegevenswijziging automatisch een update activeert.
Vierde fout — setState aanroepen met een asynchrone callback (async-lambda). Zoals beschreven in de sectie over asynchroniteit, worden wijzigingen na await niet opgevangen, wat leidt tot moeilijk te reproduceren bugs. Gebruik een synchrone callback en roep setState aan na await.
mounted in asynchrone callbacksVeelgestelde vragen
setState() meldt Flutter dat de interne gegevens van StatefulWidget zijn gewijzigd en de UI moet worden herbouwd. De methode accepteert een callback, voert deze synchroon uit, markeert de widget als dirty en plant de build-aanroep in het volgende frame.
De UI wordt niet bijgewerkt. Flutter volgt veldwijzigingen niet automatisch. De veldwaarde verandert in het geheugen, maar de widget blijft in de vorige toestand tot de volgende gedwongen herbouw door de ouder.
Nee. Dit leidt tot een oneindige lus: build roept setState aan, die de widget als dirty markeert en opnieuw build aanroept. Flutter blokkeert een dergelijke situatie niet — de applicatie crasht met StackOverflowError.
Build wordt één keer uitgevoerd. Flutter verzamelt alle dirty-elementen en bouwt ze batchgewijs aan het einde van het frame. De tweede setState vóór verwerking van de eerste voegt het element gewoon aan dezelfde lijst van dirty-elementen toe — er vindt geen herhaalde build plaats.
mounted — een booleaanse vlag die aangeeft dat de widget nog in de boom zit. Als u na een asynchrone bewerking setState aanroept zonder mounted te controleren en de widget is al verwijderd — crasht de applicatie met de uitzondering „setState called after dispose”.
Samenvatting
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.
Lees ook