Inloggen

Waarom we onze Flutter-app weggooiden en Cliqer Remote native herbouwden, op een Rust-core

We bouwden Cliqer Remote in Flutter, maten het op een echte telefoon en herbouwden het daarna native op één Rust-core: de helft van de batterij, geen warmte. De cijfers en de lessen.

Cliqer Team

5 oktober 2026 · 16 min lezen

A presenter holds a phone remote: one half seen through a thermal camera glowing hot, the other half cool in mint stage light

Op 26 september begonnen we met het bouwen van Cliqer Remote, onze telefoonapp voor presentatoren, in Flutter. Vijf dagen later stond hij op TestFlight en op de interne track van Google Play, met elke functie van de webapp voor presentatoren: de presentatieclicker, het live scherm, inkt, chat, notities en gesprekken. Het werkte. Toen maten we hem op echte telefoons. We zagen er één een oppervlaktetemperatuur van 42 °C bereiken terwijl hij alleen een gedeeld scherm toonde, en gooiden hem weg.

Niemand buiten ons team heeft die app ooit gezien. Wat je in plaats daarvan krijgt, is een native app op elk platform: SwiftUI op iPhone, iPad en Apple Watch, Kotlin en Jetpack Compose op Android en Wear OS, en daaronder één engine, één keer geschreven in Rust. Dit is het verhaal van het waarom, met de cijfers die we onderweg hebben gemeten.

De belangrijkste punten
  • Een presentatieclicker heeft een vreemde taak voor een telefoonapp: een uur lang in een warme hand liggen, live video van het scherm tonen en nooit een tik missen. Frametijd, warmte en grootte tellen zwaarder dan bij de meeste apps.
  • Onze Flutter-build was snel geschreven en zag er goed uit, maar de glaseffecten en het videopad kostten de GPU elk frame. Op een Galaxy S24+ brachten we één scherm terug van 19,8 ms naar 5,7 ms GPU-werk per frame, en nog steeds vochten we tegen de engine in plaats van het product te bouwen.
  • Daarna zetten we drie builds op dezelfde telefoon, in dezelfde ruimte, elk tien minuten kijkend naar hetzelfde bewegende scherm. De eerste Flutter-build tekende 47,6 frames per seconde, warmde de telefoon 5,9 °C op en verbruikte 168 mAh. De native app tekende helemaal geen UI-frames, warmde hem 0,2 °C op en verbruikte 86 mAh: de helft van de batterij.
  • De native apps zijn kleiner: 10,8 MB voor de iPhone-app inclusief de Apple Watch-app (de Flutter-versie was 27,6 MB) en een Android-download van 5,8 MB (Flutter: 15,6 MB), op dezelfde manier gemeten.
  • In dezelfde ronde maakten we de desktopapp tien keer sneller in het omzetten van een klik naar een dia: 48 ms in PowerPoint en 42 ms in Keynote op een Mac, waar het eerder ongeveer een halve seconde duurde.

Een presentatieclicker is een vreemde app om te bouwen

De meeste apps worden in korte sessies gebruikt. Een presentatieclicker niet. Die ligt een hele presentatie lang in één hand, vaak onder podiumlicht, met het scherm aan en de radio continu bezig. Bij Cliqer toont hij ook een live beeld van het scherm van de presentator, tekent hij inkt op de dia's, verzorgt hij chat en notities en kan hij de presentator in beeld brengen als de host daartoe uitnodigt.

Die taakomschrijving legt drie regels op:

  1. Elke tik moet aankomen, en wel nu. Een klik die te laat aankomt betekent een spreker die zwijgend voor een publiek staat.
  2. De telefoon moet koel blijven. Een warme telefoon throttlet, en een gethrottelde telefoon laat frames vallen, decodeert video te laat en trekt zijn batterij sneller leeg. Hij is bovendien onprettig om een uur vast te houden.
  3. De app moet klein zijn en snel starten. Mensen installeren hem op locatie, vaak via de conferentiewifi, minuten voordat ze het podium op gaan.

Flutter kan alle drie voor de meeste apps. Onze app bleek het soort app waarbij het meer kost dan het oplevert.

Wat Flutter goed deed

Eer wie eer toekomt. Flutter bracht ons in vijf dagen van een lege map naar een complete app in beide stores, met één codebase, hot reload, uitstekende testmogelijkheden en een UI die er overal hetzelfde uitzag. We schreven er 266 tests voor. Bouw je een app met formulieren en lijsten, een dashboard of een webshop, dan raden we het nog steeds zonder aarzelen aan.

We hebben er ook veel van geleerd. Bijna elke bevinding in dit artikel kwam voort uit het tracen van die Flutter-build op een echte telefoon, en een deel van de fixes zit nu in elke Cliqer-client, ook in de webversie.

Waar het pijn deed: glas kost per frame een heel scherm

De look van Cliqer is mat glas: doorschijnende panelen die vervagen wat erachter zit. Op iPhone en iPad was dat goedkoop. Op onze Android-testtelefoon, een Galaxy S24+ met een 120Hz-scherm, niet. Bij 120 Hz heeft een frame 8,3 ms om getekend te worden, alles bij elkaar.

We traceerden de GPU op het toestel en vonden de oorzaak in de manier waarop de rendering-engine de achtergrond inleest: elk glaspaneel kopieerde het hele scherm, elk frame, met multisampling erbovenop. Het deelnamescherm had negen glaspanelen, dus negen schermvullende kopieën per frame. De GPU in die telefoon is een ontwerp van desktopklasse dat geen tiler is: hij kan niet goedkoop teruglezen wat hij net heeft getekend, en elke nieuwe textuurgrootte betekende een nieuwe geheugentoewijzing die de kernel eerst op nul zet. In één trace ging 71% van de tijd van de rasterthread op aan het toewijzen van grafisch geheugen.

We losten het op, scherm voor scherm. We stapten over op een prereleaseversie van de engine, zodat een blur tot zijn eigen paneel kon worden begrensd (van 41 ms naar 17 ms). We bundelden de negen panelen op het deelnamescherm tot één gedeelde leesactie (van 19,8 ms naar 5,7 ms). We stopten met het animeren van de grootte van glasoppervlakken, omdat elke nieuwe grootte een nieuwe toewijzing was, en bootsten de animaties in plaats daarvan na met lagen van vaste grootte. Het slechtste frame van de QR-scanner ging van 285 ms naar 18 ms.

Elk van die fixes was een workaround voor de engine, geen functie voor een presentator. En met glas goed voor 80 tot 90% van de GPU-tijd in een ruimte zaten we steeds maar één nieuw paneel verwijderd van opnieuw wegvallende frames.

Waar het echt pijn deed: video, en de warmte die dat oplevert

Het gedeelde scherm is het zwaarste wat de app toont. In de Flutter-build werd elk gedecodeerd videoframe als textuur aan de UI doorgegeven, wat betekende dat de UI zichzelf voor elk videoframe opnieuw tekende: de ruimte, het glas, de bediening, alles, 45 keer per seconde, alleen om de pixels binnen één rechthoek te verplaatsen.

We maten dat voor het eerst op 1 oktober op de Galaxy, met systeemtraces. Terwijl de video speelde, draaide de GPU rond 440 MHz. Bij een diawissel duurden frames 25 tot 33 ms en zat de GPU gedurende meer dan de helft van de overgang tegen zijn plafond van 800 MHz. De telefoon stond op de thermische systeemstatus ‘moderate’, met een oppervlaktetemperatuur van 42 °C, en begrensde zijn eigen GPU om het bij te benen. Warmte liet onze metingen ook liegen: een warme telefoon scoorde ongeveer 20% trager dan een koele, dus bij elke vergelijking moesten we de builds afwisselen bij dezelfde temperatuur.

Daarna probeerden we het voor de hand liggende experiment: de video op een native laag zetten die het besturingssysteem zelf samenstelt, buiten de UI. Zelfde telefoon, zelfde app, zelfde ruimte: de UI hield op met opnieuw tekenen voor video, de GPU-klok zakte van 440 naar 121 MHz en het frame bij een diawissel ging van 25 tot 33 ms naar 3 tot 7 ms. We brachten die fix op 1 oktober uit in de Flutter-app. Het liet ons ook zien waar het echte antwoord lag: in een native app staat video altijd op een systeemlaag, op iPhone en op Android, dus deze hele categorie problemen verdwijnt door het ontwerp zelf in plaats van via een workaround.

Zelfde telefoon, zelfde ruimte, tien minuten

Beweringen over warmte zijn goedkoop, dus we hebben het gemeten. Op 5 oktober zetten we drie builds op dezelfde Galaxy S24+, na elkaar, elk tien minuten, zonder lader, in dezelfde ruimte, kijkend naar hetzelfde bewegende gedeelde scherm. Voor elke run koelde de telefoon weer af.

  1. Flutter, eerste build: de app zoals we hem eerst bouwden, met video getekend door de UI.
  2. Flutter, geoptimaliseerd: dezelfde app na de glas- en videofixes hierboven.
  3. Native: de nieuwe Cliqer Remote.

De eerste Flutter-build tekende zijn hele interface 47,6 keer per seconde opnieuw, alleen om een video te tonen, hield de GPU op gemiddeld 366 MHz en gebruikte 1,47 seconde CPU-tijd per seconde. De native app tekende in tien minuten geen enkel interfaceframe: alleen de videolaag bewoog. Zijn GPU zat gemiddeld op 129 MHz, zijn CPU op 0,69 seconde per seconde, en hij verbruikte 86 mAh batterij tegenover 168 mAh. Dat is de helft van de batterij voor hetzelfde werk.

En dan de warmte. In tien minuten bracht de eerste Flutter-build de batterij van 36,2 °C naar 42,1 °C, en begon de telefoon zichzelf te throttlen. De geoptimaliseerde Flutter-build steeg 1,9 °C. De native app steeg 0,2 °C. Een presentatie duurt langer dan tien minuten, en dat verschil voel je in je hand.

Om eerlijk te zijn tegenover de geoptimaliseerde Flutter-build: die kwam dichtbij. Onze eigen fixes binnen Flutter haalden het grootste deel van de kosten weg, en native won met een kleinere marge: 12% minder CPU en 25% minder batterij. Het gaat erom waar elk van beide begint. De eerste build was hoe een goede Flutter-app er standaard uitziet; native haalt het koele resultaat zonder de workarounds.

Zo hebben we gemeten, zodat je het kunt herhalen: Perfetto-traces op het toestel (frametijdlijn, GPU-frequentie, batterijtellers), proces-CPU-tijd uit de kernel en de eigen batterijtemperatuur van de telefoon, met bij elke run dezelfde animatie van 60 fps op het gedeelde scherm. Eén telefoon en één run per build, dus zie de cijfers als een zorgvuldige praktijktest, niet als een laboratoriumbenchmark. De native run begon ook zo'n 2 °C koeler, en daarom vergelijken we de stijging.

Wat we in plaats daarvan bouwden

De nieuwe Cliqer Remote is native op elk platform, gebouwd op één gedeelde engine.

De interface is native op elk platform. Op iPhone en iPad is het SwiftUI met het eigen Liquid Glass van het systeem. Op Android is het Jetpack Compose, met echte achtergrondvervaging waar het platform dat toelaat. De Apple Watch- en Wear OS-apps zijn ook native, klein genoeg om Volgende en Terug op je pols te hebben.

De engine is één keer geschreven, in Rust. Deelnemen aan een ruimte, opnieuw verbinden en hervatten na een netwerkwissel, de schermvideo ontvangen, klikken, laser en inkt versturen, chat, notities, de timer, gesprekken en het koppelen van een telefoon aan een computer zitten allemaal in één Rust-bibliotheek die beide apps delen. Elke app tekent alleen de interface, decodeert video met de hardwaredecoder van het systeem en praat met de camera en microfoon. Een protocolfix wordt één keer doorgevoerd en komt op beide platforms uit, en de twee apps kunnen niet uit elkaar groeien.

Rust was de logische keuze voor die engine: geen garbage collector die een frame pauzeert, geheugenveiligheid zonder runtime, en het compileert naar één kleine bibliotheek voor zowel iOS als Android. Het is snel waar het telt. Op een Mac verpakt onze engine een H.264-frame van 20 KB in 31 µs, voltooit hij een beveiligde handshake in 198 µs per kant en geeft hij in onze tests een videoframe van 100 KB in 46 µs door aan de Android-interface (voordat we die grens optimaliseerden, was dat 440 µs). Via een relay duurde een round trip in onze tests mediaan 13 tot 16 ms.

Ook nog kleiner

We hebben beide builds op dezelfde manier gemeten. De iPhone-app zoals geüpload naar Apple ging van 27,6 MB naar 10,8 MB, en dat is inclusief de Apple Watch-app (1,5 MB). De download voor een Android-telefoon, gemeten met Googles eigen bundletool voor dezelfde 64-bitstelefoon, ging van 15,6 MB naar 5,8 MB. Ongeveer een derde van de oude grootte maakt verschil wanneer iemand de app vijf minuten voor aanvang via de wifi van de locatie installeert.

De jacht op milliseconden stopte niet bij de telefoon

Een snellere telefoonapp is maar de helft van een klik. De andere helft is de computer waarop de presentatie draait, en terwijl de apps werden herbouwd, pakten we ook die kant aan.

Klikken: van een halve seconde naar een tiende

Wanneer een klik de Cliqer-desktopapp bereikt, moet die de presentatie verder zetten en daarna iedereen in de ruimte laten weten welke dia er te zien is. Voor dat tweede deel vroeg de app na elke klik het dianummer op bij de presentatiesoftware, en die vraag was traag.

Cliqer 2.3 houdt één verbinding met PowerPoint of Keynote open en leest de positie direct af uit de stap die hij net heeft gezet. Op een Mac daalde de mediane tijd van klik tot dia over 20 runs van 509 ms naar 48 ms in PowerPoint en van 408 ms naar 42 ms in Keynote. Tien snelle drukken, zoals een presentator die maakt om een onderdeel over te slaan, hadden vroeger 5,3 seconden nodig om tot rust te komen. Nu worden ze samengevoegd tot één beweging en staan ze binnen 254 ms op hun plek.

Video die bijblijft op slechte wifi

Eerder dit jaar ontdekten we waarom een gedeeld scherm op wifi seconden lang kon bevriezen. Schermvideo reist in Cliqer via een betrouwbaar datakanaal, en de standaard waarop dat kanaal is gebouwd wacht minstens één volle seconde voordat een verloren pakket opnieuw wordt verzonden, en verdubbelt die wachttijd bij elke herhaling. Chrome gebruikt 400 ms. Elk frame achter het verloren pakket stond in de rij, de host perste vervolgens een volledige verversing in diezelfde wachtrij, en op een trage verbinding gebeurde dat keer op keer.

We stelden de timers voor opnieuw verzenden af op de waarden van Chrome en voorkwamen dat verversingen achter de achterstand in de rij belandden. In een netwerksimulatie met een wifi-verliesmodel ging een 1080p-deling van 15 tot 22 bevriezingen van een seconde of langer per twee minuten naar 0 tot 2, en de mediane achterstand op live van tot wel 2,3 seconden naar 12 ms. We begrensden volledige verversingen ook op één per stream per halve seconde, omdat elke verversing naar elke kijker gaat: vóór die grens kon een kijker 19 volledige verversingen per seconde ontvangen, zo'n 40 Mbit/s.

Aan de telefoonkant hield de hardwarevideodecoder van de Galaxy ongeveer één keer per seconde een gewoon frame zo'n 60 ms vast. Door de decoder op volle snelheid te laten draaien, kwamen we op nul late frames in 50 seconden. Die fix zit in de native Android-app.

Wat we een ander team zouden vertellen

Bouw je iets realtime op telefoons? Deze lessen kostten ons dagen om te leren en kosten jou minuten om toe te passen:

  1. Meet op je slechtste echte toestel, niet op de emulator. Onze emulator draaide op 60 Hz met een andere grafische backend en kon geen enkel van de glasproblemen laten zien.
  2. Wissel builds af bij dezelfde temperatuur. Een warme telefoon scoorde ongeveer 20% trager. Een koude run vergelijken met een warme run zet je op het verkeerde been.
  3. Tel je blur-leesacties. Elke achtergrondblur kan een schermvullende kopie per frame kosten. Bundel ze, begrens ze en animeer nooit de grootte van een vervaagd oppervlak.
  4. Teken je UI nooit opnieuw voor video. Zet video op een laag die het systeem samenstelt. Het was de grootste winst op GPU, batterij en warmte die we hebben gemeten.
  5. Vraag de hardwaredecoder om realtime snelheid. Stel op Android de operating rate van de decoder in. Eén regel haalde een hapering van 60 ms per seconde weg.
  6. Controleer de timers van je transportlaag. Betrouwbare datakanalen erven hun timers voor opnieuw verzenden van oudere standaarden. Een minimum van één seconde is een eeuwigheid voor live video.
  7. Behandel volledige verversingen als de dure frames die het zijn. Begrens ze per stream, want elke verversing gaat naar elke kijker.
  8. Voeg invoerbursts samen. Tien drukken moeten één beweging van tien zijn, geen tien round trips.
  9. Lees wat je tools in je build schrijven. Een prerelease-toolchain zette zonder te vragen een renderingvlag aan in ons Android-manifest.

Wat dit voor jou betekent

10x

sneller van klik tot dia in PowerPoint en Keynote op een Mac

5.8 MB

Android-download, ongeveer een derde van de oude grootte

10.8 MB

iPhone-app inclusief de Apple Watch-app

Half

het batterijverbruik van onze eerste Flutter-build, zelfde telefoon, zelfde 10 minuten

Cliqer Remote is native op iPhone, iPad, Android, Apple Watch en Wear OS. Klikken gaan sneller, de telefoon blijft koel en verbruikt de helft van de batterij die onze eerste build gebruikte, de video wordt getekend zoals de telefoon daarvoor ontworpen is, en de app is ongeveer een derde van zijn oude grootte. De webapp voor presentatoren werkt nog steeds in elke browser, helemaal zonder installatie, dus niemand in je ruimte valt ooit buiten de boot.

Download de apps

Cliqer Remote voor iPhone, iPad, Android en je horloge.

Latentie van presentatieclickers: waarom milliseconden tellen

Wat er echt gebeurt tussen een tik en de dia die verspringt.

Hoe we schermdelen vier keer sneller maakten

Het andere grote prestatieverhaal van dit jaar.

Waarom we onze realtime-infrastructuur weghaalden bij Cloudflare

Dezelfde jacht op stabiliteit, aan de serverkant.

Veelgestelde vragen

flutterrustnative appsswiftuijetpack composeperformancelatencyengineering

Geschreven door het Cliqer Team

Wij bouwen Cliqer, de internetclicker die wereldwijd op podia, in klaslokalen en in vergaderzalen wordt gebruikt.

Draai je volgende show met Cliqer

Alle slides, elke telefoon, overal. De desktop-app host de ruimte, presentatoren openen gewoon een link.

Verder lezen

Meer productnieuws voor je volgende show

A laptop running Cliqer Studio and a phone running Cliqer Remote on a backstage tech table, the stage projection wall glowing behind them
Productnieuws

Waarom Cliqer 3 native is: wat versie 2 probeerde en waarom een native engine wint

Waarom Cliqer 3 native is: een Rust-engine, hardwarevideo, echte beeldschermuitgangen en NDI in plaats van een webapp in een desktopschil.
A dedicated server rack glowing mint in a dark data hall
Productnieuws

Waarom we de realtime van Cliqer bij Cloudflare weghaalden (en waarom we nog steeds fan zijn)

Vier maanden op de edge van Cloudflare: wat er misging, wat we bouwden om ermee om te gaan en waarom Cliqer nu op onze eigen dedicated servers draait.
A software engineer reviewing a video latency graph on a monitor in a dimly lit office
Productnieuws

Hoe we schermdelen vier keer sneller maakten

Het technische verhaal achter hoe we de latentie bij schermdelen in Cliqer terugbrachten van meer dan twee seconden naar ongeveer een halve seconde.
Nieuwsbrief

Ontvang de volgende gids vóór je volgende show

Praktische gidsen voor presentatoren, AV-crews en eventteams, plus de Cliqer releases die ertoe doen. Geen spam, met één klik afmelden.