Menu
Coddy logo textTech

Sealed Classes (Dart 3)

Part of the Object Oriented Programming section of Coddy's Dart journey — lesson 85 of 110.

Dart 3 introduced sealed classes, which restrict which classes can extend or implement them. When you mark a class as sealed, only classes in the same file can be its direct subtypes. This creates a closed set of known subtypes.

sealed class Result {}

class Success extends Result {
  final String data;
  Success(this.data);
}

class Failure extends Result {
  final String error;
  Failure(this.error);
}

The power of sealed classes comes from exhaustiveness checking. When you switch on a sealed type, the compiler knows all possible subtypes and ensures you handle each one:

String handleResult(Result result) {
  return switch (result) {
    Success(data: var d) => 'Got: $d',
    Failure(error: var e) => 'Error: $e',
  };
  // No default needed - compiler knows all cases are covered!
}

void main() {
  var success = Success('Hello');
  var failure = Failure('Not found');
  
  print(handleResult(success));  // Got: Hello
  print(handleResult(failure));  // Error: Not found
}

If you add a new subtype to a sealed class, the compiler will flag every switch statement that doesn't handle it. This makes sealed classes ideal for modeling states, results, or any scenario where you have a fixed set of variants. Unlike abstract classes, sealed classes guarantee that no external code can add unexpected subtypes.

challenge icon

Challenge

Easy

Let's build a network request handler using sealed classes! You'll create a type-safe system that models different states of a network response, ensuring every possible outcome is handled properly.

You'll organize your code into two files:

  • response.dart: Create a sealed class hierarchy that represents all possible states of a network response:
    • A sealed class called NetworkResponse that serves as the base for all response types
    • A Loading class that extends NetworkResponse with a String message field (e.g., "Fetching data...")
    • A Success class that extends NetworkResponse with a String data field and an int statusCode field
    • An Error class that extends NetworkResponse with a String errorMessage field and an int errorCode field
  • main.dart: Import your response file and create a function that handles all response types using a switch expression:
    • Create a function handleResponse(NetworkResponse response) that returns a String
    • Use a switch expression with pattern matching to handle each case:
      • For Loading: return Status: [message]
      • For Success: return Success ([statusCode]): [data]
      • For Error: return Error ([errorCode]): [errorMessage]
    • Create three response instances and print the result of handling each:
      • A Loading with message Please wait...
      • A Success with data User profile loaded and status code 200
      • An Error with error message Not found and error code 404

The beauty of sealed classes is that the compiler ensures you handle every possible response type - no default case needed!

Expected output:

Status: Please wait...
Success (200): User profile loaded
Error (404): Not found

Cheat sheet

Sealed classes restrict which classes can extend or implement them. Only classes in the same file can be direct subtypes, creating a closed set of known variants.

sealed class Result {}

class Success extends Result {
  final String data;
  Success(this.data);
}

class Failure extends Result {
  final String error;
  Failure(this.error);
}

Sealed classes enable exhaustiveness checking in switch expressions. The compiler knows all possible subtypes and ensures you handle each one:

String handleResult(Result result) {
  return switch (result) {
    Success(data: var d) => 'Got: $d',
    Failure(error: var e) => 'Error: $e',
  };
  // No default case needed - all cases are covered
}

If you add a new subtype, the compiler flags every switch statement that doesn't handle it. This makes sealed classes ideal for modeling states, results, or any fixed set of variants where you want compile-time guarantees that all cases are handled.

Try it yourself

import 'response.dart';

// TODO: Create a function handleResponse(NetworkResponse response)
// that returns a String
// Use a switch expression with pattern matching to handle each case:
// - For Loading: return "Status: [message]"
// - For Success: return "Success ([statusCode]): [data]"
// - For Error: return "Error ([errorCode]): [errorMessage]"

void main() {
  // TODO: Create a Loading instance with message "Please wait..."
  
  // TODO: Create a Success instance with data "User profile loaded" and status code 200
  
  // TODO: Create an Error instance with error message "Not found" and error code 404
  
  // TODO: Print the result of handling each response
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming