Dart Fundamentals: The Language Behind Flutter · Classes, Modern Dart 3 Features and Async
Dart 3: Records, Patterns and Sealed Classes
Return several values with records, destructure data with patterns, write exhaustive switch expressions, and model states with sealed classes so the compiler checks you handled every case.
What you'll build
A network request result modelled as a sealed class (Loading, Success, Failure), rendered with an exhaustive switch expression, plus functions that return records and destructure JSON-like maps with patterns.
The idea
Dart 3 added features that make data-heavy code short and safe:
- Records: lightweight, immutable groups of values:
(int, String)or with names({double lat, double lng}). Great for returning several values:(min, max) = range(values);. - Patterns: destructure records, lists, maps and objects:
var [first, ...rest] = list;,if (json case {'name': String name, 'age': int age}) .... - Switch expressions:
final label = switch (code) { 200 => 'ok', 404 => 'missing', _ => 'other' };. - Sealed classes: a closed family of subtypes, all in one library. A
switchover a sealed type must handle every subtype, or the code doesn't compile, just like Rust's enums.
The code
sealed class FetchState {}
class Loading extends FetchState {}
class Success extends FetchState {
final List<String> items;
Success(this.items);
}
class Failure extends FetchState {
final int status;
final String message;
Failure(this.status, this.message);
}
String render(FetchState state) => switch (state) {
Loading() => 'Loading…',
Success(items: []) => 'Nothing here yet',
Success(:final items) => 'Showing ${items.length}: ${items.join(', ')}',
Failure(status: 401) => 'Please sign in again',
Failure(:final status, :final message) => 'Error $status: $message',
}; // adding a new subclass makes this a compile error until it's handled
(double, double, double) stats(List<double> values) {
final sorted = [...values]..sort();
final avg = values.reduce((a, b) => a + b) / values.length;
return (sorted.first, sorted.last, avg);
}
String describeUser(Map<String, Object?> json) {
if (json case {'name': String name, 'age': int age} when age >= 18) {
return '$name (adult)';
}
if (json case {'name': String name}) {
return '$name (age unknown or minor)';
}
return 'invalid user';
}
void main() {
final states = [Loading(), Success([]), Success(['milk', 'eggs']), Failure(401, 'expired'), Failure(503, 'try later')];
for (final s in states) {
print(render(s));
}
final (low, high, average) = stats([21.5, 19.0, 24.0, 22.5]); // destructure a record
print('low $low, high $high, average ${average.toStringAsFixed(2)}');
final location = (lat: 38.72, lng: -9.14); // a record with named fields
print('Lisbon at ${location.lat}, ${location.lng}');
final [first, second, ...rest] = ['gold', 'silver', 'bronze', 'fourth', 'fifth'];
print('$first, $second, and ${rest.length} more');
for (final json in [{'name': 'Ana', 'age': 34}, {'name': 'Leo', 'age': 12}, {'title': 'oops'}]) {
print(describeUser(json));
}
}
Output
Loading… Nothing here yet Showing 2: milk, eggs Please sign in again Error 503: try later low 19.0, high 24.0, average 21.75 Lisbon at 38.72, -9.14 gold, silver, and 3 more Ana (adult) Leo (age unknown or minor) invalid user
Run this on your computer — see “Get Started” for setup.
Common mistakes
- Using
_in switches over sealed types. It hides new subtypes; list them so the compiler can help. - Records for long-lived domain models. Records are great for small groupings; use classes when you need methods and names that document intent.
- Forgetting that records compare by value.
(1, 2) == (1, 2)is true, unlike plain classes. - Over-complex patterns. Split very nested patterns into helper functions for readability.
Key points
- Records
(a, b)/(x: .., y: ..)group values and compare by value. - Patterns destructure lists, maps, records and objects (
case,whenguards). - Switch expressions return values; sealed classes make them exhaustive.
- Sealed classes are ideal for UI states: loading, success, failure.
Further reading: Records, Patterns, Sealed classes.
Practice questions
What do sealed classes give you in Dart 3?
Answer
A closed set of subtypes defined in the same library. The compiler knows every subtype, so a switch over the sealed type must handle all of them; adding a subtype produces compile errors wherever it isn't handled.
What are Dart records?
Answer
Anonymous, immutable aggregates of values with positional and/or named fields, e.g. (int, String) or ({double lat, double lng}). They're typed, compare by value, and are handy for returning multiple values.
Open in the app to mark lessons complete, take notes and track your progress.