Quand des centaines d’acheteurs se disputent une enchère de fruits et légumes en direct, l’horloge à l’écran est la seule chose qui sépare une bonne affaire d’une occasion manquée. Le prix baisse toutes les 50 millisecondes. Hésitez, et quelqu’un d’autre enchérit avant vous. Affichez un prix périmé, et l’acheteur enchérit sur quelque chose qu’il n’avait pas prévu.
Nous avons construit l’horloge d’enchères en temps réel pour une plateforme belge qui dessert trois grandes criées. La première version a été livrée rapidement et fonctionnait correctement : un cadran circulaire peint sur mesure avec 100 points de prix, des mises à jour en direct via WebSocket, et une soumission d’enchères précise. Elle faisait ce qu’elle devait faire.
Mais « fonctionne correctement » et « donne la bonne sensation » sont deux choses différentes. Au fur et à mesure que la plateforme prenait de l’ampleur et que nous observions de vraies sessions d’enchères, nous avons vu des possibilités d’amélioration. L’horloge se mettait à jour 20 fois par seconde, ce qui est techniquement suffisant. Mais ce n’était pas fluide. Et sur les appareils plus lents, nous avons détecté un écart subtil entre ce que les acheteurs voyaient et ce que le système calculait au moment de l’enchère. Les deux étaient des occasions de relever la barre.
Nos pistes d’amélioration
Le point avançait par sauts, pas en mouvement continu. Le point actif avait 100 positions discrètes et sautait de l’une à l’autre 20 fois par seconde. Fonctionnellement correct, mais visuellement saccadé. Sur les grands écrans, où chaque saut couvre plus de pixels, l’effet était encore plus visible. Les acheteurs le décrivaient comme « nerveux ».
Les appareils lents introduisaient un décalage temporel. Sur les tablettes d’entrée de gamme tournant à 5-10fps, le prix affiché à l’écran pouvait accuser un retard de 100 à 200 millisecondes par rapport à la réalité. À la vitesse d’une enchère, cela représente quatre paliers de prix. Si un acheteur appuyait sur « enchérir », une implémentation naïve recalculait le prix à partir de l’heure système et envoyait une valeur différente de celle affichée. Nous l’avons détecté tôt, mais il fallait une solution robuste et pérenne.
Le déclic
Les deux pistes menaient au même levier architectural : l’horloge traitait le rendu comme une opération unique à une fréquence unique.
Toutes les 50 millisecondes, le système recalculait le prix, recolorait les 100 points, repositionnait 10 étiquettes et déplaçait le point actif en un seul appel de dessin. Entre ces mises à jour, rien ne bougeait. Le modèle de rendu était discret là où une partie devait être continue.
Reconnaître cette distinction est ce qui a rendu l’optimisation évidente. Tout sur l’horloge ne change pas au même rythme et ne coûte pas le même effort à dessiner. Une fois ces préoccupations séparées, les solutions ont suivi naturellement.
Ce que nous avons construit
Séparer le pipeline de rendu
Nous avons séparé l’horloge en deux painters avec des fréquences de mise à jour indépendantes. Le travail coûteux (100 points, 10 étiquettes, coloration conditionnelle) reste au rythme naturel de l’horloge. Le travail léger (un seul point en mouvement) tourne au rythme de rafraîchissement de l’écran. Chaque painter se redessine indépendamment. Le point n’attend jamais le cadran.
Interpolation fractionnaire
Au lieu de sauter entre 100 positions entières, le point actif interpole désormais entre elles avec une précision sub-step à chaque image. La position du point est calculée comme une valeur continue à chaque rafraîchissement d’écran, pas seulement aux limites de tick. Cela donne au point 3 fois plus de positions distinctes par demi-seconde, transformant des sauts visibles en un mouvement fluide.
Protection par gel à l’enchère
Quand un acheteur appuie sur enchérir, nous gelons l’horloge de manière synchrone et capturons exactement le prix qu’il voyait — pas une valeur recalculée à partir de l’heure système. Flutter traite les événements gestuels avant les callbacks d’animation dans son pipeline de frames. Nous exploitons cette garantie pour arrêter l’horloge à l’instant où le bouton d’enchère est touché, avant que la frame suivante ne puisse faire avancer le prix. L’enchère correspond toujours à ce qui était affiché.
Intelligence de saut de frames
Un mécanisme de filtrage saute les frames d’animation où rien n’aurait pu changer, réduisant les calculs inutiles de 50 % sans manquer un seul palier de prix. À 60fps, le ticker se déclenche toutes les 16ms. Mais le prix ne change que toutes les 50ms. Le filtre saute les frames qui tombent entre les limites de tick, réduisant les calculs superflus sur toutes les horloges visibles tout en gardant une marge suffisamment serrée pour ne jamais manquer un palier.
Les résultats
Le point actif se déplace désormais à 60 images par seconde. Un mouvement fluide et continu, quel que soit le rythme de tick de l’horloge. Le cadran de fond se redessine toujours à son rythme naturel, gardant l’utilisation CPU sous contrôle. Le mécanisme de gel à l’enchère garantit zéro dérive de prix sur n’importe quel appareil, vérifié par des tests automatisés simulant des scénarios de frames lentes.
Nous avons validé l’ensemble du système en charge : 500 utilisateurs simultanés, de vraies connexions WebSocket, du streaming audio en temps réel, des enchères arrivant en même temps. Les horloges sont restées stables.
Ce que cela nous a confirmé
Trois principes se sont dégagés de ce travail, applicables à toute application Flutter en temps réel :
Ce ne sont pas des astuces. Ce sont le type de décisions qui viennent d’une expérience approfondie du moteur de rendu de Flutter et du travail sur des systèmes où la justesse et la fluidité comptent toutes les deux.
Vous développez une application Flutter en temps réel et souhaitez aller plus loin en performance ?