ZetCode

Dart Sealed Classes

last modified June 5, 2025

This tutorial explores Dart sealed classes, demonstrating their use in creating type-safe, exhaustive hierarchies for state management and pattern matching.

A sealed class in Dart is a special abstract class marked with the sealed keyword. It restricts its subclasses to be defined within the same library, enabling the compiler to know all possible subclasses. This allows for exhaustive pattern matching in switch expressions, ensuring all cases are handled, which enhances type safety and reduces runtime errors.

Dart Sealed Class Overview

Sealed classes are ideal for modeling finite state hierarchies, such as UI states, network responses, or algebraic data types. They leverage Dart's pattern matching to handle each subclass concisely, often with switch expressions that destructure properties for direct access.

Feature Description Example
Sealed Restricts subclasses to same library sealed class Shape {}
Subclasses Fixed set of implementations class Circle extends Shape {}
Pattern Matching Exhaustive switch handling switch (shape) { case Circle(): ... }

Basic Sealed Class for Shapes

This example defines a sealed class hierarchy for geometric shapes, using pattern matching to calculate their areas.

shapes.dart
sealed class Shape {}

class Circle extends Shape {
  final double radius;
  Circle(this.radius);
}

class Square extends Shape {
  final double side;
  Square(this.side);
}

class Triangle extends Shape {
  final double base;
  final double height;
  Triangle(this.base, this.height);
}

double calculateArea(Shape shape) {
  switch (shape) {
    case Circle(:final radius):
      return 3.14 * radius * radius;
    case Square(:final side):
      return side * side;
    case Triangle(:final base, :final height):
      return 0.5 * base * height;
  }
}

void main() {
  var circle = Circle(5);
  var square = Square(4);
  var triangle = Triangle(3, 6);

  print('Circle area: ${calculateArea(circle)}');
  print('Square area: ${calculateArea(square)}');
  print('Triangle area: ${calculateArea(triangle)}');
}

The Shape sealed class has three subclasses: Circle, Square, and Triangle. The calculateArea function uses a switch expression to compute areas, destructuring properties like radius for concise access. The compiler ensures all cases are covered, preventing unhandled states.

$ dart run shapes.dart
Circle area: 78.5
Square area: 16.0
Triangle area: 9.0

Network Request States

Sealed classes are useful for modeling network request states, such as loading, success, or error conditions, with type-safe handling.

http_state.dart
sealed class HttpState {}

class Loading extends HttpState {}

class Error extends HttpState {
  final String message;
  Error(this.message);
}

class Loaded extends HttpState {
  final String data;
  Loaded(this.data);
}

void handleState(HttpState state) {
  switch (state) {
    case Loading():
      print('⏳ Loading...');
    case Loaded(:final data):
      print('✅ Success! Response: $data');
    case Error(:final message):
      print('❌ Error: $message');
  }
}

void main() {
  handleState(Loading());
  handleState(Loaded('Data loaded successfully!'));
  handleState(Error('Failed to load data.'));
}

The HttpState sealed class represents network states. Loading indicates an ongoing request, Loaded holds response data, and Error stores an error message. The handleState function uses pattern matching to process each state, ensuring all possibilities are handled exhaustively.

$ dart run http_state.dart
⏳ Loading...
✅ Success! Response: Data loaded successfully!
❌ Error: Failed to load data.

Sealed Class with Factory Constructors

Sealed classes can use factory constructors to create instances of subclasses, providing a clean API for result handling.

result.dart
sealed class Result<T> {
  const Result();
  factory Result.success(T value) = Success<T>;
  factory Result.failure(String error) = Failure<T>;
}

class Success<T> implements Result<T> {
  final T value;
  Success(this.value);
}

class Failure<T> implements Result<T> {
  final String error;
  Failure(this.error);
}

void processResult(Result<int> result) {
  switch (result) {
    case Success(:final value):
      print('Success with value: $value');
    case Failure(:final error):
      print('Failure with error: $error');
  }
}

void main() {
  Result<int> successResult = Result.success(42);
  Result<int> failureResult = Result.failure('An error occurred');

  processResult(successResult);
  processResult(failureResult);
}

The Result<T> sealed class models success or failure outcomes with a generic type. Factory constructors Result.success and Result.failure create Success or Failure instances. The processResult function uses pattern matching to handle results, accessing the value or error directly.

$ dart run result.dart
Success with value: 42
Failure with error: An error occurred

User Authentication States

Sealed classes can model user authentication states, such as unauthenticated, authenticated, or guest access, with type-safe handling.

auth_state.dart
sealed class AuthState {}

class Unauthenticated extends AuthState {}

class Authenticated extends AuthState {
  final String userId;
  Authenticated(this.userId);
}

class Guest extends AuthState {
  final String sessionId;
  Guest(this.sessionId);
}

void handleAuth(AuthState state) {
  switch (state) {
    case Unauthenticated():
      print('🔓 Please log in to continue.');
    case Authenticated(:final userId):
      print('👤 Logged in as user: $userId');
    case Guest(:final sessionId):
      print('🌐 Guest access with session: $sessionId');
  }
}

void main() {
  handleAuth(Unauthenticated());
  handleAuth(Authenticated('user123'));
  handleAuth(Guest('session456'));
}

The AuthState sealed class defines authentication states. Unauthenticated represents no login, Authenticated includes a user ID, and Guest holds a session ID. The handleAuth function processes each state using pattern matching, ensuring all cases are covered.

$ dart run auth_state.dart
🔓 Please log in to continue.
👤 Logged in as user: user123
🌐 Guest access with session: session456

Mathematical Expressions

Sealed classes can represent mathematical expressions, enabling recursive evaluation with pattern matching for complex calculations.

expression.dart
sealed class Expression {}

class Number extends Expression {
  final double value;
  Number(this.value);
}

class Add extends Expression {
  final Expression left;
  final Expression right;
  Add(this.left, this.right);
}

class Multiply extends Expression {
  final Expression left;
  final Expression right;
  Multiply(this.left, this.right);
}

double evaluate(Expression expr) {
  switch (expr) {
    case Number(:final value):
      return value;
    case Add(:final left, :final right):
      return evaluate(left) + evaluate(right);
    case Multiply(:final left, :final right):
      return evaluate(left) * evaluate(right);
  }
}

void main() {
  var expr = Add(
    Number(2),
    Multiply(Number(3), Number(4)),
  );
  print('Result: ${evaluate(expr)}');
}

The Expression sealed class models a mathematical expression tree with Number, Add, and Multiply subclasses. The evaluate function recursively computes the result using pattern matching, handling the expression 2 + (3 * 4) to yield 14.

$ dart run expression.dart
Result: 14.0

Source

Dart Sealed Classes - language reference

This tutorial demonstrated Dart sealed classes, showcasing their power in creating type-safe, exhaustive hierarchies for state management and pattern matching.

Author

My name is Jan Bodnar, a passionate programmer with extensive experience. Since 2007, I have authored over 1,400 articles and 8 e-books, with more than ten years of teaching programming.

List all Dart tutorials.