Wanneer honderden kopers strijden in een live groente- en fruitveiling, is de klok op hun scherm het enige dat tussen een goede deal en een gemiste kans staat. De prijs daalt elke 50 milliseconden. Even aarzelen en iemand anders biedt eerst. Een verouderde prijs zien en je biedt op iets wat je niet bedoelde.
We bouwden de realtime veilingklok voor een Belgisch platform dat drie grote veilinghuizen bedient. De eerste versie werd snel opgeleverd en werkte correct: een custom-painted cirkelvormige wijzerplaat met 100 prijspunten, live WebSocket-updates en nauwkeurige biedingen. Het deed wat het moest doen.
Maar “werkt correct” en “voelt goed” zijn twee verschillende dingen. Naarmate het platform groeide en we echte veilingsessies observeerden, zagen we ruimte om de ervaring te verbeteren. De klok werd 20 keer per seconde bijgewerkt, wat technisch snel genoeg is. Het voelde alleen niet vloeiend aan. En op tragere apparaten merkten we een subtiel verschil op tussen wat kopers zagen en wat het systeem berekende op het moment van bieden. Beide waren kansen om de lat hoger te leggen.
Waar We Verbeterpotentieel Zagen
Het punt bewoog in stappen, niet vloeiend. Het actieve punt had 100 discrete posities en sprong 20 keer per seconde tussen die posities. Functioneel correct, maar visueel schokkerig. Op grotere schermen, waar elke sprong meer pixels beslaat, was het effect duidelijker zichtbaar. Kopers omschreven het als “schokkerig.”
Trage apparaten introduceerden een timingverschil. Op goedkopere tablets die op 5-10fps draaiden, kon de weergegeven prijs 100-200 milliseconden achter de werkelijkheid aanlopen. Op veilingsnelheid zijn dat vier prijsstappen. Als een koper op “bied” tikte, zou een naïeve implementatie de prijs herberekenen op basis van de actuele tijd en een andere waarde versturen dan wat er op het scherm stond. We ontdekten dit vroeg, maar het had een robuuste, permanente oplossing nodig.
Het Inzicht
Beide verbetermogelijkheden wezen naar dezelfde architecturale hefboom: de klok behandelde rendering als één enkele operatie op één enkele frequentie.
Elke 50 milliseconden herberekende het systeem de prijs, kleurde alle 100 punten opnieuw, herpositioneerde 10 labels en verplaatste het actieve punt in één paint-aanroep. Tussen die updates bewoog er niets. Het renderingmodel was discreet, terwijl een deel ervan continu moest zijn.
Het herkennen van dit onderscheid maakte de optimalisatie eenvoudig. Niet alles op de klok verandert even snel of kost evenveel om te tekenen. Zodra we die zaken scheidden, volgden de oplossingen vanzelf.
Wat We Bouwden
De Renderingpipeline Opgesplitst
We splitsten de klok op in twee painters met onafhankelijke updatefrequenties. Het zware werk (100 punten, 10 labels, conditionele kleuring) blijft op het natuurlijke ticktempo van de klok. Het lichte werk (één bewegend punt) draait op de schermverversingsfrequentie. Elke painter hertekent onafhankelijk. Het punt wacht nooit op de wijzerplaat.
Fractionele Interpolatie
In plaats van te snappen naar 100 gehele posities, interpoleert het actieve punt nu tussen posities met sub-stap precisie op elk frame. De puntpositie wordt berekend als een continue waarde bij elke schermverversing, niet alleen bij tick-grenzen. Dit geeft het punt 3x meer onderscheidende posities per halve seconde, waardoor zichtbare sprongen veranderen in vloeiende beweging.
Bied-Bevriezing
Wanneer een koper op bied tikt, bevriezen we de klok synchroon en leggen exact de prijs vast die ze zagen — niet een herberekende waarde op basis van de actuele tijd. Flutter verwerkt gesture-events vóór animatie-callbacks in zijn frame-pipeline. We gebruiken die garantie om de klok te stoppen op het moment dat de biedknop wordt ingedrukt, voordat het volgende frame de prijs kan laten verspringen. Het bod komt altijd overeen met wat er op het scherm stond.
Frame-Skip Intelligentie
Een gating-mechanisme slaat animatieframes over waarin niets veranderd kan zijn, waardoor verspilde berekeningen met 50% worden verminderd zonder ook maar één prijsstap te missen. Op 60fps vuurt de ticker elke 16ms. Maar de prijs verandert pas elke 50ms. De gate slaat frames over die tussen tick-grenzen vallen, waardoor onnodige berekeningen over alle zichtbare klokken worden verminderd, terwijl de marge krap genoeg blijft om nooit een stap te missen.
De Resultaten
Het actieve punt beweegt nu op 60 frames per seconde. Vloeiende, continue beweging ongeacht hoe snel de klok tikt. De achtergrondwijzerplaat hertekent nog steeds op zijn natuurlijke snelheid, waardoor het CPU-gebruik beheersbaar blijft. Het bied-bevriezingsmechanisme garandeert nul prijsafwijking op elk apparaat, geverifieerd door geautomatiseerde tests die langzame-frame scenario’s simuleren.
We valideerden het volledige systeem onder belasting: 500 gelijktijdige gebruikers, echte WebSocket-verbindingen, realtime audiostreaming, biedingen die tegelijkertijd binnenkwamen. De klokken bleven stabiel.
Wat Dit Bevestigde
Drie principes sprongen eruit bij dit werk, die van toepassing zijn op elke realtime Flutter-applicatie:
Dit zijn geen trucjes. Het zijn het soort beslissingen die voortkomen uit diepe ervaring met Flutter’s rendering engine en uit het werken aan systemen waar zowel correctheid als vloeiendheid ertoe doen.
Bouw je een realtime Flutter-applicatie en wil je de performance naar een hoger niveau tillen?