Dart 3 en pratique : Records, Pattern Matching et Sealed Classes pour Flutter
Guide complet sur les records, le pattern matching et les sealed classes de Dart 3 pour Flutter. Exemples concrets, bonnes pratiques et questions d'entretien technique avancees.

Avec la sortie de Dart 3, le langage qui propulse Flutter a franchi un cap en matiere de securite de type et d'expressivite. Les records, le pattern matching et les sealed classes ne constituent pas de simples ajouts cosmetiques : ces fonctionnalites redefinissent la facon de structurer les donnees, de gerer les etats et d'ecrire du code robuste des la compilation. En entretien technique, la maitrise de ces mecanismes est devenue un marqueur de competence incontournable. Ce guide detaille chaque fonctionnalite, illustre leur combinaison dans des scenarios reels et presente les questions les plus frequemment posees lors des entretiens Flutter en 2026.
Dart 3 introduit trois piliers complementaires : les records (types de donnees legers a egalite structurelle), le pattern matching (destructuration declarative avec verification exhaustive) et les sealed classes (hierarchies fermees garantissant que chaque variante est traitee). Ces mecanismes eliminent des categories entieres de bugs sans recourir a des bibliotheques externes.
Records : retours multiples sans boilerplate
Avant Dart 3, renvoyer plusieurs valeurs depuis une fonction imposait de creer une classe dediee, de recourir a un Map<String, dynamic> ou de dependre d'un package tiers comme tuple. Chacune de ces approches ajoutait du bruit au code. Les records resolvent ce probleme en offrant un type anonyme, immutable et structurellement comparable, directement integre au langage.
Un record positionnel convient aux retours simples ou la position des valeurs suffit a les identifier. Pour les structures plus riches, les champs nommes apportent une lisibilite nettement superieure et reduisent le risque d'inversion de parametres.
// Returning multiple values with a positional record
(String, int) parseUserInput(String raw) {
final parts = raw.split(':');
return (parts[0].trim(), int.parse(parts[1]));
}
// Named fields improve readability for complex returns
({String name, String email, bool isVerified}) fetchUserProfile(String id) {
// Simulated database lookup
return (
name: 'Alice Chen',
email: 'alice@example.com',
isVerified: true,
);
}L'un des atouts majeurs des records reside dans leur egalite structurelle. Deux records contenant des valeurs identiques sont consideres comme egaux, sans qu'il soit necessaire de surcharger l'operateur == ni d'utiliser un package comme equatable. Cette propriete simplifie les tests unitaires et les comparaisons d'etat dans les applications Flutter.
// Structural equality — no need for custom == operator
void main() {
final a = (1, 'hello');
final b = (1, 'hello');
print(a == b); // true — records compare by value
// Named fields also support equality
final profile1 = (name: 'Alice', role: 'admin');
final profile2 = (name: 'Alice', role: 'admin');
print(profile1 == profile2); // true
}Les records ne possedent pas d'identite d'objet. Il est impossible d'y ajouter des methodes, d'implementer des interfaces ou de les etendre par heritage. Pour toute logique metier qui doit accompagner les donnees, une classe reste l'outil approprie. Les records sont concus pour le transport de donnees et les retours multiples, pas pour l'encapsulation de comportements.
Pattern Matching : une destructuration verifiee a la compilation
Le pattern matching de Dart 3 depasse largement la simple destructuration. Il combine extraction de valeurs, verification de types et conditions de garde dans une syntaxe declarative dont la correction est garantie par le compilateur.
La destructuration de records elimine les acces repetes par index ou par nom de champ. Les patterns de liste avec l'operateur rest (...) permettent d'extraire les premiers elements d'une collection de maniere concise et sure.
// Destructuring a record with pattern matching
void main() {
final (name, email, isVerified) = fetchUserProfile('u-123');
// name, email, isVerified are now local variables
// List patterns with rest operator
final scores = [98, 87, 92, 76, 84];
final [first, second, ...remaining] = scores;
print('Top two: $first, $second'); // 98, 87
print('Others: $remaining'); // [92, 76, 84]
}Expressions switch : la fin des branches oubliees
Les expressions switch de Dart 3 marquent une rupture avec les instructions switch classiques. Elles retournent une valeur, prennent en charge les clauses de garde via when et gerent le pattern matching sur les types d'objets. Le compilateur refuse la compilation si un cas n'est pas couvert, ce qui supprime une categorie entiere de bugs lies aux branches manquantes.
// Switch expression with guard clauses
String classifyScore(int score) => switch (score) {
>= 90 => 'Excellent',
>= 80 => 'Good',
>= 70 => 'Average',
>= 60 => 'Below Average',
_ => 'Needs Improvement',
};
// Object pattern matching with type checking
String describeValue(Object value) => switch (value) {
int n when n < 0 => 'Negative integer: $n',
int n => 'Positive integer: $n',
String s when s.isEmpty => 'Empty string',
String s => 'String of length ${s.length}',
List l when l.isEmpty => 'Empty list',
List l => 'List with ${l.length} elements',
_ => 'Unknown type',
};Ces expressions switch remplacent avantageusement les chaines de if-else dans de nombreux contextes Flutter, notamment pour la construction de widgets selon l'etat de l'application.
Sealed Classes : des hierarchies d'etats infaillibles
Les sealed classes definissent un ensemble ferme de sous-types connus du compilateur. Ce dernier peut alors verifier que chaque expression switch couvre l'integralite des variantes. Un sous-type oublie declenche une erreur de compilation -- pas un bug decouvert en production apres un deploiement.
Ce mecanisme se prete particulierement bien a la modelisation des etats d'une application. L'exemple ci-dessous definit les quatre etats possibles d'un flux d'authentification.
// Sealed class defining all possible authentication states
sealed class AuthState {}
class AuthInitial extends AuthState {}
class AuthLoading extends AuthState {}
class AuthAuthenticated extends AuthState {
final String userId;
final String displayName;
AuthAuthenticated({required this.userId, required this.displayName});
}
class AuthError extends AuthState {
final String message;
final int? statusCode;
AuthError({required this.message, this.statusCode});
}Du cote du widget qui consomme cet etat, le switch exhaustif garantit que chaque variante est traitee. Si un cinquieme etat est ajoute ulterieurement -- par exemple AuthMfaRequired -- le compilateur signale immediatement chaque point du code qui doit etre mis a jour.
// Exhaustive switch — compiler error if a case is missing
Widget buildAuthUI(AuthState state) => switch (state) {
AuthInitial() => const LoginPrompt(),
AuthLoading() => const CircularProgressIndicator(),
AuthAuthenticated(
displayName: final name
) => Text('Welcome, $name'),
AuthError(
message: final msg,
statusCode: final code,
) => ErrorBanner(
message: msg,
code: code,
),
};La combinaison sealed class et switch exhaustif elimine le recours aux clauses default ou aux cas _ generiques. Chaque chemin d'execution est rendu explicite, ce qui ameliore la maintenabilite et sert de documentation vivante du code.
La synergie des trois fonctionnalites
La veritable force de Dart 3 emerge lorsque records, pattern matching et sealed classes sont utilises conjointement. Un type de resultat generique construit avec une sealed class peut encapsuler les donnees de succes accompagnees de metadonnees sous forme de record, ainsi que les erreurs avec leurs details.
// Generic sealed result type for API operations
sealed class ApiResult<T> {}
class ApiSuccess<T> extends ApiResult<T> {
final T data;
final ({int statusCode, Map<String, String> headers}) metadata;
ApiSuccess(this.data, {required this.metadata});
}
class ApiFailure<T> extends ApiResult<T> {
final String error;
final int? statusCode;
ApiFailure(this.error, {this.statusCode});
}Cote widget, le pattern matching traite chaque combinaison d'etat et de donnees avec une precision chirurgicale. Les patterns imbriques extraient les metadonnees du record tout en filtrant par code HTTP, tandis que les clauses de garde distinguent les listes vides des listes remplies.
// Consuming the sealed result with pattern matching
Widget buildProductList(ApiResult<List<Product>> result) => switch (result) {
ApiSuccess(
data: final products,
metadata: (statusCode: 200, headers: _),
) when products.isNotEmpty => ListView.builder(
itemCount: products.length,
itemBuilder: (_, i) => ProductCard(products[i]),
),
ApiSuccess(data: final products) when products.isEmpty =>
const EmptyState(message: 'No products found'),
ApiFailure(statusCode: 401) =>
const AuthExpiredBanner(),
ApiFailure(error: final msg) =>
ErrorDisplay(message: msg),
};Ce pattern elimine les verifications manuelles de type, les casts explicites et les branches else non testees. Chaque chemin d'execution est type et verifie a la compilation.
Extraction null-safe depuis des structures JSON
Le pattern matching s'applique egalement a l'extraction securisee de donnees depuis des structures JSON, un cas d'usage recurrent lors du traitement de reponses API dans les applications Flutter.
// If-case for null-safe extraction
void processUser(Map<String, dynamic> json) {
if (json case {'name': String name, 'age': int age}) {
// name and age are non-nullable here
print('$name is $age years old');
} else {
print('Invalid user data');
}
}La construction if-case verifie simultanement la presence des cles, leur type et extrait les valeurs dans des variables locales non-nullables. Ce pattern remplace les enchainements de containsKey et de casts manuels qui rendaient le code fragile et difficile a maintenir.
Prêt à réussir tes entretiens Flutter ?
Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.
Questions d'entretien technique avancees
Les questions suivantes sont regulierement posees lors d'entretiens techniques Flutter pour evaluer la comprehension approfondie des fonctionnalites de Dart 3.
En quoi un record differe-t-il d'une classe en Dart 3 ?
Un record est un type anonyme a egalite structurelle, immutable par nature. Deux records contenant les memes valeurs sont egaux sans implementation explicite de l'operateur ==. Une classe possede une identite d'objet : deux instances avec des valeurs identiques ne sont pas egales par defaut. Les records ne supportent ni methodes, ni heritage, ni implementation d'interfaces. Les records conviennent au transport de donnees et aux retours multiples ; les classes sont reservees a l'encapsulation de logique metier.
Pourquoi privilegier les sealed classes aux enums pour la gestion d'etat ?
Les enums ne peuvent pas porter de donnees specifiques a chaque variante. Un AuthState sous forme d'enum ne permettrait pas d'associer un userId uniquement a la variante authenticated. Les sealed classes combinent la verification d'exhaustivite (comme les enums) avec la capacite de transporter des donnees heterogenes par variante. Le compilateur garantit que chaque sous-type est couvert dans les expressions switch.
Comment le pattern matching s'integre-t-il avec la null safety ?
Le pattern matching de Dart 3 fonctionne en symbiose avec le systeme de null safety. La construction if-case avec des patterns de type extrait des variables garanties non-nullables, sans recourir a l'operateur bang (!). Les patterns de map verifient simultanement la presence de cles et le type de leurs valeurs, eliminant les acces non securises aux structures dynamiques.
Quel avantage offrent les expressions switch par rapport aux instructions switch ?
Les expressions switch retournent une valeur, ce qui les rend exploitables dans des assignations, des retours de fonction et des parametres de constructeur. Elles imposent l'exhaustivite : le compilateur refuse la compilation si un cas est absent. Les instructions switch traditionnelles n'offrent aucune de ces garanties. Dans le contexte Flutter, les expressions switch sont particulierement adaptees aux patterns de type builder ou chaque etat doit produire un widget.
Quel est l'impact des records sur les performances ?
Les records sont alloues sur le heap comme les classes, mais leur egalite structurelle evite la creation d'objets intermediaires pour les comparaisons. Le compilateur peut optimiser les records positionnels de petite taille plus agressivement que les classes equivalentes. En pratique, la difference de performance est negligeable pour la grande majorite des cas d'usage. Le benefice principal reside dans la reduction du boilerplate et l'amelioration de la lisibilite, ce qui diminue les bugs et accelere les cycles de developpement.
Strategie de migration progressive
Pour les equipes qui maintiennent des bases de code existantes, la transition vers les fonctionnalites de Dart 3 se realise par etapes. Tenter de tout convertir en une seule passe est contre-productif.
Premiere etape : identifier les fonctions qui retournent des Map, des List<dynamic> ou des tuples improvises pour les remplacer par des records. Ce refactoring est local et n'entraine pas de modifications en cascade dans le reste du code.
Deuxieme etape : cibler les hierarchies de classes utilisees pour modeliser des etats finis. Les classes abstraites avec un ensemble connu de sous-types constituent des candidates ideales pour devenir des sealed classes. La conversion provoque des erreurs de compilation la ou un cas n'est pas traite, revelant ainsi les branches manquantes.
Troisieme etape : remplacer les chaines de if-else et les instructions switch par des expressions switch avec pattern matching. Cette transformation ameliore la lisibilite et ajoute la verification d'exhaustivite du compilateur.
Dart 3.10 introduit les dot shorthands, une syntaxe allege qui permet d'omettre le nom du type lorsque le contexte le rend evident. Par exemple, Color color = .red au lieu de Color color = Color.red. Cette fonctionnalite reduit le bruit syntaxique et s'integre naturellement avec le pattern matching.
Conclusion
Les records, le pattern matching et les sealed classes de Dart 3 transforment en profondeur la maniere de concevoir les applications Flutter. Les points essentiels a retenir pour les entretiens et la pratique quotidienne :
- Les records fournissent des types de donnees structurelles legers avec egalite par valeur, eliminant le besoin de classes boilerplate pour les retours multiples
- Le pattern matching offre une destructuration declarative avec verification de type, clauses de garde et exhaustivite garantie par le compilateur
- Les sealed classes definissent des ensembles fermes de sous-types, rendant impossible l'oubli d'un cas dans une expression switch
- La combinaison de ces trois fonctionnalites permet de modeliser des flux complexes (authentification, appels API, navigation) avec une securite de type complete
- L'extraction null-safe via
if-caseremplace les verifications manuelles de cles et les casts non securises dans le traitement de JSON - La migration depuis du code Dart 2 se fait incrementalement : records pour les retours multiples, sealed classes pour les etats finis, expressions switch pour les branches conditionnelles
Passe à la pratique !
Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.
Tags
Partager
Articles similaires

Top 20 questions d'entretien Flutter pour développeurs mobiles
Préparez vos entretiens Flutter avec les 20 questions les plus posées. Widgets, state management, Dart, architecture et bonnes pratiques expliqués en détail.

State Management Flutter : Riverpod vs BLoC - Guide Comparatif Complet
Comparaison approfondie entre Riverpod et BLoC pour la gestion d'état Flutter. Architecture, performances, testabilité et cas d'usage pour choisir la meilleure solution.

Flutter : Créer votre première app cross-platform
Guide complet pour créer une application mobile cross-platform avec Flutter et Dart. Widgets, state management, navigation et bonnes pratiques pour débutants.