Sealed hierarchies plus pattern matching give algebraic data types, ideal for states, results and events.
A `sealed` class is implicitly abstract and can only be extended or implemented within its own library. Because the compiler knows every direct subtype, a switch over a sealed type is checked for exhaustiveness:
sealed class Result<T> {}
class Ok<T> extends Result<T> { Ok(this.value); final T value; }
class Err<T> extends Result<T> { Err(this.error); final Object error; }switch (r) { Ok(:final value) => ..., Err(:final error) => ... } needs no default, and adding a new subclass turns every incomplete switch into a compile error. This is the Dart 3 way to model UI states (Loading, Loaded, Failed), API results and events.
Dart 3 also adds modifiers that control how classes may be used outside their library:
base— can be extended but not implemented; subclasses must bebase,finalorsealed. Guarantees your constructor and private members run.interface— can be implemented but not extended.final— neither extended nor implemented outside the library.abstract— can't be instantiated; combine with the others (abstract interface classis a pure interface).mixin class— usable as both a class and a mixin.
Inside the declaring library these restrictions don't apply. Use them when publishing packages to keep the freedom to evolve classes without breaking users.
Example
sealed class LoadState {}
class Loading extends LoadState {
Loading(this.progress);
final double progress;
}
class Loaded extends LoadState {
Loaded(this.items);
final List<String> items;
}
class Failed extends LoadState {
Failed(this.message);
final String message;
}
String render(LoadState s) => switch (s) { // exhaustive: no default
Loading(:final progress) => 'Loading ${(progress * 100).round()}%',
Loaded(items: []) => 'Nothing yet',
Loaded(:final items) => '${items.length} items',
Failed(:final message) => 'Error: $message',
};
void main() {
print(render(Loading(0.25))); // Loading 25%
print(render(Loaded([]))); // Nothing yet
print(render(Loaded(['a', 'b']))); // 2 items
print(render(Failed('offline'))); // Error: offline
}Remember
sealed= closed set of subtypes in one library, enabling exhaustive switches- Object patterns like
Loaded(:final items)destructure fields base,interface,finalrestrict extends/implements outside the library
Watch out for
- Splitting a sealed hierarchy across files without
part, which isn't allowed — subtypes must be in the same library.