Dart Sealed Classes

Summary: In this tutorial, you’ll learn about the Dart sealed classes and how to use them to represent a fixed set of types.

Introduction to Dart sealed classes #

Dart 3 introduced sealed classes. When you define a sealed class, only classes within the same library can extend it.

The sealed class and its subclasses allow the Dart compiler to know all possible subtypes at compile time, allowing you to perform exhaustive checks in switch statements.

To define a sealed class, you add the sealed keyword before the class definition.

For example, the following defines a sealed class representing a network request’s state.

sealed class HttpState {
}Code language: Dart (dart)

Typically, a network request results in three possible states:

  • Loading – the request is in progress.
  • Error – the request is rejected with an error.
  • Loaded – the request is successful and resolved with data.

We can define the three subclasses of the HttpState class:

class Loading extends HttpState {
}

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

class Loaded extends HttpState {
   final String data;
   Loaded(this.data);
}Code language: Dart (dart)

Now, you can use the switch statement with exhaustiveness guaranteed:

void handleState(HttpState state) {
  switch (state) {
    case Loading():
      print('⏳ Loading...');
      break;

    case Loaded(:final data):
      print('✅ Success! Response: $data');
      break;

    case Error(:final message):
      print('❌ Error: $message');
      break;
  }
}Code language: Dart (dart)

In the handleState() function, Dart ensures that the switch handle all subtypes of the State class. If you forget one, you’ll encounter a compile-time error.

The following branch matches the Loaded class, extracts its data field and bind it to a local and final variable named data:

case Loaded(:final data)Code language: Dart (dart)

It is shorthand for the following:

case Loaded(data: final data):Code language: Dart (dart)

A similar logic is applied to the Error case:

case Error(:final message):Code language: Dart (dart)

Using switch expressions #

It’s possible to use a pattern matching with a switch expression:

String processState(State state) => switch (state) {
  Loading() => 'Loading...',
  Loaded(:var data) => 'Data: $data',
  Error(:var message) => 'Error: $message',
};Code language: Dart (dart)

In this example, we bind a data field to a mutable variable data:

Loaded(:var data) => 'Data: $data',Code language: Dart (dart)

If you omit the var keyword, Dart implicitly uses final as the default. For example:

Loaded(:data) => 'Data: $data',Code language: Dart (dart)

Dart sealed class example #

Here’s a complete example that uses a sealed class to model HTTP request states:

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);
}

Future<HttpState> fetchData({bool succeed = true}) async {
  // Simulate network delay
  await Future.delayed(Duration(seconds: 2));

  if (succeed) {
    return Loaded('{"message": "Hello from server!"}');
  } else {
    return Error('Failed to fetch data.');
  }
}

void handleState(HttpState state) {
  switch (state) {
    case Loading():
      print('⏳ Loading...');
      break;

    case Loaded(:final data):
      print('✅ Success! Response: $data');
      break;

    case Error(:final message):
      print('❌ Error: $message');
      break;
  }
}

void main() async {
  HttpState state = Loading();
  handleState(state);

  // Fetching data, change to false to simulate error
  state = await fetchData(succeed: true);
  handleState(state);
}Code language: Dart (dart)

Output:

⏳ Loading...
✅ Success! Response: {"message": "Hello from server!"}
Code language: Dart (dart)

In this example, we simulate a network request in the fetchData() function. You can change the succeed to true to simulate an error.

Dart sealed class use cases #

Dart sealed classes can be helpful in the following use cases:

  • Represent a fixed set of types such as states, shapes, and results.
  • Enhance type safety.
  • Use with pattern matching for expressive control flow.

Summary #

  • A sealed class allows only its subclasses within the same library that extends it.
  • Use the sealed keyword to define a sealed class.
  • Use a sealed class and its subclasses to represent a fixed set of types, such as states, shapes, and results.
Was this tutorial helpful ?