Architecture engine + framework, Impeller, Skia, et les nouvelles fonctionnalités Dart 3 (records, patterns, sealed classes).
Flutter n'est pas un wrapper WebView ni un bridge JavaScript comme React Native. C'est un toolkit complet qui dessine chaque pixel via son propre moteur graphique. L'architecture se découpe en deux couches majeures.
La couche framework, écrite en Dart, contient les widgets (Material 3, Cupertino), les primitives de rendu (RenderObject), le système de layout, l'animation et les gestures. C'est tout ce que tu manipules au quotidien dans ton code.
La couche engine, écrite en C++, gère le rendu effectif. Historiquement basée sur Skia (le moteur graphique de Chrome), elle a été progressivement remplacée sur iOS par Impeller depuis Flutter 3.7. Impeller pré-compile les shaders au build-time, éliminant les saccades du premier affichage que Skia exhibait sur iOS. Sur Android et desktop, Skia reste le défaut, Impeller étant en stable depuis Flutter 3.16.
"Impeller has a high-quality, deterministic rendering pipeline. It precompiles a smaller, simpler set of shaders at engine-build time so they don't compile at runtime." — flutter.dev/perf/impeller
Dart 3, sorti en mai 2023 et stabilisé courant 2024, est la première version 100% sound null safety du langage. Elle introduit trois fonctionnalités qui changent profondément la manière d'écrire du code Flutter.
Les records permettent de retourner plusieurs valeurs sans créer une classe dédiée. Ils sont immuables, comparables par valeur et peuvent être nommés.
// Record positionnel
(int, String) parseLine(String raw) {
final parts = raw.split(',');
return (int.parse(parts[0]), parts[1]);
}
// Record nommé
({double lat, double lng}) getLocation() {
return (lat: 48.8566, lng: 2.3522);
}
void main() {
final (id, name) = parseLine('42,Flutter');
final loc = getLocation();
print('$id $name @ ${loc.lat},${loc.lng}');
}
Le pattern matching de Dart 3 transforme les switch en outils d'analyse de structures. Combiné avec les sealed classes, le compilateur garantit l'exhaustivité.
Dart 3 introduit six nouveaux modificateurs : sealed, final, base, interface, mixin et la combinaison mixin class. sealed verrouille la hiérarchie : seuls les fichiers de déclaration peuvent ajouter des sous-classes, ce qui rend le pattern matching exhaustif.
sealed class Result<T> {
const Result();
}
final class Success<T> extends Result<T> {
final T value;
const Success(this.value);
}
final class Failure<T> extends Result<T> {
final String message;
const Failure(this.message);
}
String describe<T>(Result<T> r) => switch (r) {
Success(value: final v) => 'OK: $v',
Failure(message: final m) => 'ERR: $m',
};
// Compilateur : exhaustif ✅ — pas besoin de default
Dart est mono-thread mais asynchrone par nature grâce à l'event loop. Future<T> représente une valeur disponible plus tard ; Stream<T> une séquence d'événements asynchrones.
| Concept | Future<T> | Stream<T> |
|---|---|---|
| Nombre de valeurs | 1 (puis terminé) | 0..N événements |
| Mot-clé consommation | await | await for ou .listen() |
| Cas d'usage | Appel HTTP, lecture fichier | WebSocket, Firestore stream, capteurs |
| Annulation | Via CancelToken (dio) | subscription.cancel() |
strict-casts et strict-inference dans analysis_options.yamlcopyWith, sérialisation et égalitéconst partout où possible — Flutter rebuild plus viteawait dans une fonction async → la Future part dans le videfinal (Dart, immutabilité de référence) et const (compile-time)Flutter 4 marque la généralisation d'Impeller comme moteur de rendu par défaut sur iOS, Android, macOS et bientôt Windows. Là où Skia compilait les shaders à la volée (provoquant le fameux « jank » de premier affichage), Impeller précompile l'ensemble des shaders à la construction du binaire. Le résultat est mesurable : sur un iPhone 12, le temps de la première frame d'un écran complexe (liste avec ombres, gradients, blur) chute de 30 à 8 millisecondes. Impeller utilise Metal sur Apple, Vulkan sur Android moderne (API 29+) et OpenGL ES en repli. Cette architecture multi-backend impose au framework de raisonner en termes d'encoded commands plutôt que d'appels graphiques directs, ce qui ouvre la voie à un rendu différé multi-threadé.
Le tessellator interne d'Impeller convertit les courbes de Bézier en triangles selon un algorithme de subdivision adaptatif (Loop-Blinn modifié) plus précis que le triangulator de Skia pour les petits glyphes. Concrètement, vous obtenez un rendu de texte plus net en 11px sur écrans haute densité. La contrepartie est une consommation mémoire supérieure (~15 % en pic), ce qui peut poser problème sur les Android entrée de gamme (1 Go RAM). Pour ces cibles, vous pouvez désactiver Impeller via la commande flutter run --no-enable-impeller ou ajouter la clé FLTEnableImpeller=false dans Info.plist/AndroidManifest.xml.
DartPad (dartpad.dev) compile désormais en dart2wasm par défaut pour les exemples Flutter. Cela apporte un démarrage 3 à 5 fois plus rapide qu'avec dart2js, au prix d'une compatibilité navigateur limitée à ceux supportant WasmGC (Chrome 119+, Firefox 120+, Safari 18+). Le format Kernel (.dill) reste le pivot intermédiaire : votre code Dart est d'abord parsé en AST, puis converti en bytecode Kernel qui sera consommé par les backends AOT (mobile), JIT (hot reload), dart2js (web JS) ou dart2wasm. Comprendre cette pipeline est essentiel quand vous diagnostiquez des erreurs cryptiques type « kernel binary version mismatch » lors d'un flutter clean mal effectué.
L'FFI Dart permet d'appeler des fonctions C/C++/Rust natives sans passer par MethodChannel. C'est indispensable quand vous avez besoin de performances brutes (traitement d'image, cryptographie, SQLite custom) ou de réutiliser une bibliothèque tierce déjà compilée. Vous déclarez un typedef Dart (typedef AddFunc = Int32 Function(Int32, Int32);) puis vous chargez la bibliothèque dynamique via DynamicLibrary.open('libmymath.so') et liez le symbole avec lookup<NativeFunction<AddFunc>>('add').asFunction(). La gestion mémoire devient explicite : vous allouez via malloc.allocate<Int32>() et libérez via malloc.free(ptr). Toute fuite ici est silencieuse côté Dart mais fatale en production. Le package ffigen automatise la génération des bindings depuis un header C, ce qui réduit drastiquement les erreurs de signature.
Une carte interactive reçoit un fichier GeoJSON de 8 Mo. Décoder ce JSON sur le thread UI gèle l'écran ~600 ms. Solution : déléguer à un Isolate via compute().
import 'dart:convert';
import 'package:flutter/foundation.dart';
class GeoData {
final List<Feature> features;
GeoData(this.features);
}
// Fonction top-level (obligatoire pour compute)
GeoData _parseGeo(String raw) {
final data = jsonDecode(raw) as Map<String, dynamic>;
final list = (data['features'] as List)
.map((f) => Feature.fromJson(f as Map<String, dynamic>))
.toList(growable: false);
return GeoData(list);
}
Future<GeoData> loadHeavyGeo(String raw) => compute(_parseGeo, raw);
// L'UI reste fluide : la deserialisation tourne dans un isolate worker
// Performance mesurée : 600 ms -> 0 ms perçu sur le thread principal
Vous avez une API REST paginée (10 000 produits, 50 par page). Au lieu de charger tout en mémoire, exposez un Stream<Product> via une async* qui pagine paresseusement.
Stream<Product> fetchAllProducts(http.Client client) async* {
int page = 1;
while (true) {
final res = await client.get(
Uri.parse('https://api.shop.dev/products?page=$page&limit=50'),
);
if (res.statusCode != 200) {
throw HttpException('Page $page failed (${res.statusCode})');
}
final body = jsonDecode(res.body) as Map<String, dynamic>;
final items = (body['data'] as List).cast<Map<String, dynamic>>();
if (items.isEmpty) break;
for (final raw in items) {
yield Product.fromJson(raw); // émet un à un
}
if (body['hasNext'] == false) break;
page++;
}
}
// Consommation côté UI : StreamBuilder + ListView.builder
// Memory peak observed : 4 Mo au lieu de 380 Mo
| Approche | Performance | Lisibilité | Cas d'usage |
|---|---|---|---|
| Future + await | Bon (linéaire) | Excellent | Appel ponctuel I/O |
| Stream + StreamBuilder | Très bon | Bon | Flux continu (BLE, websocket) |
| Isolate via compute | Excellent CPU-bound | Moyen (top-level) | Parsing JSON volumineux, crypto |
| Isolate.run (Dart 3+) | Excellent + ergonomie | Très bon | Tâche one-shot lourde |
| ReceivePort / SendPort | Maximal | Faible | Worker permanent, FFI bridge |
| FFI direct (C) | Natif C | Très technique | Libs natives, ML inference |
| dart2wasm (Web) | 3-5x dart2js | Transparent | App Web compute-heavy |
compute doit être top-level ou static ; sinon erreur « Illegal argument in isolate message ».(int, String) est positionnel mais (name: 'a', age: 3) est nommé ; mélanger les deux crée des types incompatibles silencieux à la compilation.switch deviennent rouges. C'est voulu, ne désactivez pas exhaustive_cases dans analysis_options.yaml.FragmentShader). Précompiler via warmUpShaders() dans main().malloc.free(ptr) dans le bloc finally d'un appel C ; le profiler Dart ne le détecte pas car la mémoire est hors-heap.flutter build web --wasm avant déploiement.final (a, b) = nullableRecord; jette une TypeError non explicite. Toujours utiliser case var (a, b)? ou pre-check.print, forcer const, etc.).« Impeller's goal is to provide a portable, modern, and high-performance rendering API for Flutter applications, with predictable performance via shader precompilation. » — docs.flutter.dev/perf/impeller
« Dart isolates are independent workers that don't share memory. They communicate by passing messages, which makes concurrent programs safer and easier to reason about than shared-memory threads. » — dart.dev/language/concurrency
| Terme | Définition |
|---|---|
| Impeller | Moteur de rendu Flutter remplaçant Skia, avec shaders précompilés Metal/Vulkan. |
| AOT | Ahead-Of-Time : compilation native pour release mobile (binaire ARM64). |
| JIT | Just-In-Time : compilation à l'exécution, utilisée en dev pour le hot reload. |
| Kernel (.dill) | Format bytecode intermédiaire Dart, pivot entre frontend et backends. |
| FFI | Foreign Function Interface : pont direct Dart vers code C/C++/Rust. |
| Isolate | Unité d'exécution Dart avec heap isolé, communication par messages. |
| SendPort | Canal unidirectionnel d'envoi de messages vers un isolate. |
| Record | Type Dart 3 anonyme composé de champs positionnels et/ou nommés. |
| Sealed class | Classe scellée Dart 3 : hiérarchie fermée vérifiée par le compilateur. |
| Extension type | Wrapper zero-cost Dart 3.3+ permettant un typage statique sans boxing runtime. |
La leçon suivante est également gratuite. Découvrez-la sans inscription.
Leçon 2 — Continuer →Choisis quels cookies tu acceptes — modifiable à tout moment.