Lesson 11 · 11 of 20
Patterns and sealed classes · Dart
Model a screen's states with Dart 3 sealed classes and handle them with an exhaustive switch, object patterns, when guards and if-case.

A `sealed` class can only have subtypes in its own file. It is abstract and, in exchange, the compiler knows the complete list of its subtypes: Loading, Loaded and Failed. It is the usual way to model a screen's states in Flutter: loading, showing data or showing an error.
render uses a `switch` expression with object patterns: Loaded(:final items) checks that the state is a Loaded and pulls its items field into a variable. Patterns can be more precise: Loaded(items: []) only matches an empty list, Failed(code: 401) only that exact code, and the guard when items.length == 1 adds a condition to the case.
Order matters: cases are tried from top to bottom and the first match wins, which is why the general Loaded(:final items) comes after the specific ones. And since the class is sealed, the switch is exhaustive: it needs no default, and if you delete the Loading() case or add a new state, the program will not compile until it is covered.
Patterns are not just for classes. The last line uses if (json case ...) with a map pattern and a list pattern: it checks that there is a user key holding a String and a roles list whose first element is text (... accepts the rest), and binds the values to name and role on the way. It is a safe way to read JSON without a chain of checks and casts.
Code · main.dart
// sealed: every subtype is declared in this file, so the compiler// knows the complete list and can check that a switch covers it.sealed class LoadState {}class Loading extends LoadState {}class Loaded extends LoadState { final List<String> items; Loaded(this.items);}class Failed extends LoadState { final int code; final String message; Failed(this.code, this.message);}// Exhaustive switch: no default needed; forget a case and it won't compile.String render(LoadState state) => switch (state) { Loading() => 'Showing spinner...', Loaded(items: []) => 'Nothing here yet', Loaded(:final items) when items.length == 1 => 'One item: ${items[0]}', Loaded(:final items) => '${items.length} items: ${items.join(', ')}', Failed(code: 401) => 'Session expired, please log in', Failed(:final code, :final message) => 'Error $code: $message', };void main() { final states = [Loading(), Loaded([]), Loaded(['Milk']), Loaded(['Tea', 'Rice']), Failed(401, 'Unauthorized'), Failed(503, 'Server busy')]; for (final state in states) print(render(state)); // Patterns also check and unpack a map, such as decoded JSON. final Object json = {'user': 'carmen', 'roles': ['admin', 'editor']}; if (json case {'user': String name, 'roles': [String role, ...]}) print('$name is $role');}Output · dart run main.dart
Showing spinner... Nothing here yet One item: Milk 2 items: Tea, Rice Session expired, please log in Error 503: Server busy carmen is admin
Key points
sealedfixes the list of subtypes in the same file.- A
switchover a sealed class is exhaustive: nodefaultneeded. Class(:final field)checks the type and extracts the field.whenadds a condition to a case.if (value case pattern)validates and destructures at once.
Exercise
Add an Offline state to the sealed class, look at the compiler error in render and add the case that prints No connection.