# Flutter-prestatieoptimalisatie in 2026: Impeller, rebuilds en best practices > Hoe Flutter-apps in 2026 constant 60 of 120 fps halen met Impeller, gedisciplineerde widget-rebuilds, RepaintBoundary en profilering met DevTools. - Published: 2026-07-04 - Updated: 2026-07-07 - Author: SharpSkill - Tags: flutter, performance, impeller, best-practices, dart, mobile - Reading time: 9 min --- De prestatieoptimalisatie van Flutter in 2026 rust op twee verschuivingen die veranderden hoe soepele apps worden gebouwd: de rendering-engine Impeller is nu standaard actief op iOS en Android, en het rebuild-model van het framework beloont ontwikkelaars die widgetbomen klein en onveranderlijk houden. Teams die beide hefbomen benutten, halen constant 60 of 120 fps zonder elke frame met de hand bij te stellen. > **De meest impactvolle verandering** > > De grootste winst in de meeste Flutter-apps zit in het toevoegen van `const`-constructors en het opsplitsen van grote `build`-methoden, zodat een `setState`-aanroep één knop opnieuw tekent in plaats van een heel scherm. ## Waarom Impeller de prestatiebasislijn van Flutter opnieuw zet Impeller is de rendering-engine van Flutter en verving Skia als standaard op de mobiele platforms. Het verschil telt vooral in de eerste seconden van een animatie. Skia compileerde zijn shaders just-in-time, de eerste keer dat een effect op het scherm verscheen, wat het beruchte haperen bij de eerste keer opleverde waarom Flutter-apps werden bekritiseerd. Impeller compileert die shaders vooraf, tijdens de build, zodat de allereerste frame van een animatie net zo soepel is als de honderdste. Impeller richt zich ook rechtstreeks op moderne grafische API's: Metal op iOS en Vulkan op Android, met een OpenGL-terugval voor oudere Android-hardware. De [documentatie van de Impeller-engine op GitHub](https://github.com/flutter/flutter/blob/master/docs/engine/impeller/README.md) beschrijft de rendering-architectuur en de matrix van platformondersteuning. | Aspect | Skia (verouderd) | Impeller (standaard 2026) | |--------|---------------|-------------------------| | Shader-compilatie | Tijdens runtime, veroorzaakt haperen bij eerste keer | Vooraf, tijdens de build | | iOS-backend | OpenGL / Metal | Metal | | Android-backend | OpenGL | Vulkan, OpenGL-terugval | | Soepelheid van de eerste animatie | Af en toe haperen | Constant | Voor de meeste projecten is de migratie gratis: recente Flutter-versies schakelen Impeller automatisch in, en de oude workarounds voor shader-warmup (`--purge-persistent-cache`, gebundelde `.sksl`-bestanden) zijn niet meer nodig en kunnen worden verwijderd. Ook aangepaste `FragmentProgram`-shaders die zijn geschreven tegen de door Flutter ondersteunde GLSL-subset blijven werken, omdat Impeller ze compileert via dezelfde offline pipeline als de eigen effecten van het framework. Nagaan welke engine een app draait, kost één regel: een debug-build print de actieve backend bij het opstarten, en de DevTools-inspector meldt het bij de rendering-details van de app. Zodra Impeller is bevestigd, verschuift het resterende prestatiewerk omhoog naar de widgetlaag, waar het framework veel vaker herbouwt dan het scherm daadwerkelijk verandert. Daar richt de rest van deze gids zich op, want een snelle engine kan een widgetboom die zichzelf honderden keren per seconde opnieuw tekent niet redden. ## Flutter-rebuilds terugdringen met const en het opsplitsen van widgets Elke `setState` markeert zijn widget als verouderd en voert `build` opnieuw uit voor dat element en zijn subboom. De truc is die subboom zo smal mogelijk te houden. Twee gewoonten doen het meeste werk: statische widgets als `const` markeren en de state naar beneden duwen zodat het deel met state klein blijft. Een `const`-widget wordt gecanoniseerd tot één enkele instantie, zodat Flutter bij een rebuild van een ouder de identieke referentie vergelijkt, geen wijziging ziet en het opnieuw tekenen van die tak volledig overslaat. ```dart // product_screen.dart // A cart update repaints the badge, not the header. class ProductScreen extends StatefulWidget { const ProductScreen({super.key}); @override State createState() => _ProductScreenState(); } class _ProductScreenState extends State { int _cartCount = 0; @override Widget build(BuildContext context) { return Column( children: [ const ProductHeader(), // const: never rebuilds on setState CartBadge(count: _cartCount), // only this subtree rebuilds FilledButton( onPressed: () => setState(() => _cartCount++), child: const Text('Add to cart'), ), ], ); } } ``` De lint `prefer_const_constructors` inschakelen in `analysis_options.yaml` maakt van deze discipline een compilercontrole, zodat `const`-kansen nooit stilletjes terugvallen. De verwante regel `prefer_const_literals_to_create_immutables` breidt dezelfde garantie uit naar lijsten en maps die aan widgets worden doorgegeven. Widget-keys verdienen hier ook een vermelding. Wanneer een lijst opnieuw wordt geordend of een widget hetzelfde type behoudt maar van identiteit verandert, laat een `ValueKey` of `ObjectKey` Flutter het oude element koppelen aan de juiste nieuwe widget-instantie in plaats van de state af te breken en opnieuw op te bouwen. Ontbrekende keys zijn een veelvoorkomende oorzaak van verloren scrollposities en gereset animaties in dynamische lijsten, en het extra rebuild-werk dat ze veroorzaken is makkelijk over het hoofd te zien. ## Rebuilds beperken tot de gewijzigde data Wanneer state hoog in de boom leeft, herbouwt zelfs een goed opgesplitste layout nog veel. De oplossing is een waarde uit te zenden en één widget ernaar te laten luisteren. Flutter levert `ValueNotifier` en `ValueListenableBuilder` precies hiervoor, zonder dat een extern package nodig is. ```dart // article_list.dart // A scroll-to-top button that rebuilds alone, not the whole page. class ArticleList extends StatefulWidget { const ArticleList({super.key}); @override State createState() => _ArticleListState(); } class _ArticleListState extends State { final _showFab = ValueNotifier(false); final _controller = ScrollController(); @override void initState() { super.initState(); // Update the notifier, not setState: the list never rebuilds _controller.addListener(() => _showFab.value = _controller.offset > 400); } @override void dispose() { _showFab.dispose(); _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: ListView.builder( controller: _controller, itemCount: 200, itemBuilder: (_, i) => ListTile(title: Text('Item $i')), ), floatingActionButton: ValueListenableBuilder( valueListenable: _showFab, builder: (_, visible, __) => visible ? FloatingActionButton( onPressed: () => _controller.jumpTo(0), child: const Icon(Icons.arrow_upward), ) : const SizedBox.shrink(), ), ); } } ``` Bibliotheken voor statebeheer veralgemenen hetzelfde idee. De `ref.watch(provider.select(...))` van Riverpod en de `Selector`-widget van Provider herbouwen beide alleen wanneer een gekozen deel van de state verandert, wat op datadichte schermen de doorslag geeft tussen soepel en hakkelend. De afwegingen tussen die bibliotheken komen aan bod in de gids over [statebeheer in Flutter in 2026](/blog/flutter/flutter-state-management-2026-riverpod-bloc-getx). > **Let op de reikwijdte van de rebuild** > > `setState` aanroepen dicht bij de wortel van een scherm, of een hele pagina in een `Consumer` op topniveau wikkelen, tekent stilletjes honderden widgets opnieuw bij elke wijziging. Profileer met de rebuild-teller in DevTools voordat je aanneemt dat een scherm goedkoop is. ## Dure repaints isoleren met RepaintBoundary Herbouwen en opnieuw tekenen zijn afzonderlijke fasen. Een widget kan de rebuild overslaan en toch opnieuw tekenen omdat een rumoerige buur zijn laag deelt. `RepaintBoundary` geeft een subboom een eigen laag, zodat een geanimeerde grafiek statische kaarten niet kan dwingen elke frame opnieuw te tekenen. ```dart // dashboard.dart // The live chart repaints on its own layer, cards stay untouched. class Dashboard extends StatelessWidget { const Dashboard({required this.ticker, super.key}); final Stream ticker; @override Widget build(BuildContext context) { return Column( children: [ // SummaryCards is static, so isolate it from the animation below const RepaintBoundary(child: SummaryCards()), RepaintBoundary( child: StreamBuilder( stream: ticker, builder: (_, snap) => LivePriceChart(value: snap.data ?? 0), ), ), ], ); } } ``` RepaintBoundary is niet gratis: elk exemplaar reserveert een laag, dus het overal rondstrooien schaadt meer dan het helpt. Reserveer het voor echt geanimeerde regio's en lange scrollende lijsten, en bevestig de winst in de timeline. De [RepaintBoundary API-referentie](https://api.flutter.dev/flutter/widgets/RepaintBoundary-class.html) documenteert hoe de debug-repaint-overlay markeert welke lagen daadwerkelijk opnieuw worden getekend. ## De build-methode goedkoop en zonder neveneffecten houden Een `build`-methode draait bij elke rebuild, dus alles wat duur is erin vermenigvuldigt zich. De regel is dat `build` de state moet lezen en widgets moet teruggeven, meer niet. Een `ScrollController`, `AnimationController` of `Future` binnen `build` aanmaken, herstelt die bij elke frame en lekt de vorige instantie. Die objecten horen thuis in `initState` of in een `late final`-veld, vrijgegeven in `dispose`. Zwaar synchroon werk is de tweede valkuil. JSON parsen, een grote lijst filteren of datums formatteren binnen `build` voert dat werk bij elke repaint opnieuw uit, zelfs wanneer de onderliggende data niet is veranderd. Bereken het resultaat één keer wanneer de invoer verandert en cache het, of verplaats het naar een `FutureBuilder` zodat het framework geen frame lang wacht. Voor waarden die zijn afgeleid van andere waarden, memoïseer ze achter een getter die alleen herberekent wanneer zijn afhankelijkheden veranderen. De `Opacity`-widget is een specifieke boosdoener die het benoemen waard is. Een subboom in `Opacity` wikkelen dwingt een `saveLayer` buiten het scherm af, een van de duurste bewerkingen in de pipeline. Voor een fade-animatie zijn `AnimatedOpacity` of een `FadeTransition` goedkoper, en voor een vaste tint vermijdt het toepassen van kleur op paint-niveau via een `foregroundDecoration` of een shader de laag helemaal. Dezelfde voorzichtigheid geldt voor `ClipPath` en `ClipRRect` met anti-aliasing op grote oppervlakken, die achter de schermen eveneens een `saveLayer` uitlokken. Geef ten slotte de voorkeur aan `const`-collecties en callbacks die niet bij elke build verse closures vastleggen. Een als standaard doorgegeven `const []` en een methodereferentie in plaats van een inline-lambda houden de gelijkheidscontroles van widgets goedkoop, waardoor de eerder beschreven `const`-kortsluiting daadwerkelijk in werking treedt. ## Lange lijsten en afbeeldingen efficiënt opbouwen Lange feeds en afbeeldingsrasters zijn waar naïeve Flutter-code frames verliest. Twee regels dekken de meeste gevallen: rijen lui opbouwen en afbeeldingen decoderen op de grootte waarop ze worden getoond in plaats van op hun bronresolutie. ```dart // feed.dart // Lazy rows plus right-sized image decoding keep scrolling at 60fps. class Feed extends StatelessWidget { const Feed({required this.posts, super.key}); final List posts; @override Widget build(BuildContext context) { return ListView.builder( itemCount: posts.length, // build on demand, not all at once cacheExtent: 600, // pre-build past the viewport edge itemBuilder: (context, index) { final post = posts[index]; return ListTile( leading: Image.network( post.thumbnailUrl, width: 48, height: 48, cacheWidth: 96, // decode at display size, save memory ), title: Text(post.title), ); }, ); } } ``` Een afbeelding op volledige resolutie van 3000 pixels die in een avatar van 48 pixels wordt geperst, verspilt geheugen en decodeertijd bij elk daarvan. `cacheWidth` instellen vertelt de engine om op de doelgrootte te decoderen, wat op zichzelf al de geheugenvoetafdruk van een lijst met een orde van grootte kan verkleinen. De [best practices voor prestaties](https://docs.flutter.dev/perf/best-practices) van Flutter bundelen verdere richtlijnen over `Opacity`, `saveLayer` en clip-gebruik die hetzelfde principe volgen. ## Flutter-optimalisatie in 2026 meten met DevTools Elke bovenstaande regel moet worden geverifieerd, niet aangenomen. Flutter DevTools is de bron van waarheid. De prestatie-overlay (`P` in de debug-console, of `flutter run --profile`) tekent twee grafieken, UI en raster, en elke balk die de groene lijn overschrijdt is een verloren frame. De timeline-weergave wijst vervolgens elke trage frame toe aan een specifieke `build`-, layout- of paint-aanroep. Profileer altijd in `--profile`-modus op een fysiek apparaat. Debug-builds draaien ongeoptimaliseerd Dart en blazen elke meting op, zodat een scherm dat in debug hapert vaak perfect draait zodra het is gecompileerd. De [DevTools-prestatiegids](https://docs.flutter.dev/tools/devtools/performance) loopt door het lezen van de framegrafiek en het opsporen van haperbronnen, en dezelfde profileerreflex duikt op in sollicitatiegesprekken over [Flutter-animaties en rendering](/technologies/flutter/interview-questions/animations). > **Een snelle profileerlus** > > Draai in profile-modus, reproduceer de haperende interactie, open de timeline en sorteer de frames op duur. De slechtste frame wijst vrijwel altijd naar één te grote rebuild of een niet-gecachte afbeelding, wat veel sneller is dan gokken. De [Flutter-technologiehub](/technologies/flutter) verbindt de onderwerpen statebeheer, testen en rendering die een productieklare prestatiestrategie afronden. ## Conclusie Flutter-prestaties in 2026 draaien minder om microtrucs en meer om een korte lijst gewoonten die consistent worden toegepast: - Uitleveren op Impeller en verouderde shader-warmup-code verwijderen; het probleem van haperen bij de eerste frame is op engine-niveau opgelost. - Statische widgets als `const` markeren en dat afdwingen met de lint `prefer_const_constructors`. - Widgets met state klein opsplitsen, zodat elke `setState` de smalst mogelijke subboom opnieuw tekent. - Veranderende waarden uitzenden via `ValueListenableBuilder` of een `select`-achtige API in plaats van hele schermen te herbouwen. - Echt geanimeerde regio's in een `RepaintBoundary` wikkelen, maar meten voordat je er meer toevoegt. - Lijsten opbouwen met `ListView.builder` en afbeeldingen decoderen op weergavegrootte met `cacheWidth`. - Profileren in `--profile`-modus op echte hardware en DevTools, niet de intuïtie, laten beslissen wat als volgende geoptimaliseerd wordt. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/nl/blog/flutter/flutter-performance-optimization-2026-impeller-rebuilds