Sealed Classes and Enhanced Enums in Dart: Build Type-Safe Flutter Applications
Introduction
Modern Dart provides powerful tools for modeling application states:
- Sealed classes
- Enhanced enums
These features are extremely useful for:
- API responses
- UI states
- Authentication states
- Navigation states
- Result types
- Business logic
They help eliminate invalid states and make your code safer.
Sealed Classes
A sealed class restricts where subclasses can be created.
Example:
sealed class AuthState {
const AuthState();
}Subclasses:
class AuthLoading extends AuthState {}
class Authenticated extends AuthState {
final User user;
Authenticated(this.user);
}
class Unauthenticated extends AuthState {}
class AuthError extends AuthState {
final String message;
AuthError(this.message);
}Exhaustive Switching
Because Dart knows all possible subclasses, you can use exhaustive switching.
Widget buildAuth(AuthState state) {
return switch (state) {
AuthLoading() =>
const LoadingScreen(),
Authenticated(:final user) =>
HomeScreen(user: user),
Unauthenticated() =>
const LoginScreen(),
AuthError(:final message) =>
ErrorScreen(message: message),
};
}No default case is required.
If you add another state:
class AuthExpired extends AuthState {}the compiler can identify places where you forgot to handle it.
sealed vs abstract
An abstract class can be extended outside its library.
A sealed class provides stronger control over the hierarchy.
Use sealed classes when you want a known and controlled set of states.
Other Class Modifiers
Dart also provides:
base
interface
final
sealedThese modifiers help control inheritance and implementation.
Get Nisarg Ratani’s stories in your inbox
Join Medium for free to get updates from this writer.
For Flutter developers, the most commonly useful modifiers are:
sealedfinalbase
Understanding them helps when designing SDKs, packages, and architecture libraries.
Enhanced Enums
Traditional enums:
enum Status {
pending,
success,
failed,
}Enhanced enums allow:
- Fields
- Constructors
- Methods
- Interfaces
Example:
enum OrderStatus {
pending('Pending'),
processing('Processing'),
completed('Completed'),
cancelled('Cancelled');
const OrderStatus(this.label);
final String label;
}Usage:
print(OrderStatus.pending.label);Enum Methods
enum PaymentStatus {
pending,
completed,
failed;
bool get isCompleted {
return this == PaymentStatus.completed;
}
}Usage:
if (status.isCompleted) {
print('Payment successful');
}Enums With Behavior
enum UserRole {
admin,
manager,
employee;
bool get canManageUsers {
return switch (this) {
UserRole.admin => true,
UserRole.manager => true,
UserRole.employee => false,
};
}
}This keeps role-related logic close to the enum.
Enum Interfaces
Enhanced enums can implement interfaces.
abstract interface class Displayable {
String get label;
}Then:
enum ThemeMode implements Displayable {
light,
dark;
@override
String get label => name;
}When to Use Sealed Classes vs Enums
Use an enum when:
- You have a fixed set of simple values.
- Each value has limited associated data.
Example:
enum ThemeType {
light,
dark,
system,
}Use sealed classes when states have different data.
Example:
sealed class PaymentState {}
class PaymentLoading extends PaymentState {}
class PaymentSuccess extends PaymentState {
final Payment payment;
PaymentSuccess(this.payment);
}
class PaymentFailure extends PaymentState {
final String message;
PaymentFailure(this.message);
}Real Flutter Example: API State
sealed class AsyncState<T> {
const AsyncState();
}
class Initial<T> extends AsyncState<T> {}
class Loading<T> extends AsyncState<T> {}
class Success<T> extends AsyncState<T> {
final T data;
const Success(this.data);
}
class Failure<T> extends AsyncState<T> {
final String message;
const Failure(this.message);
}Usage:
switch (state) {
case Initial():
return const Placeholder();
case Loading():
return const CircularProgressIndicator();
case Success(:final data):
return DataView(data: data);
case Failure(:final message):
return ErrorView(message: message);
}Best Practices
Use enums for:
- Status values
- Categories
- Roles
- Small fixed options
Use sealed classes for:
- UI states
- API results
- Events
- Complex domain states
- States with different data structures
Avoid representing complex state with multiple booleans:
bool isLoading;
bool hasError;
bool isSuccess;This can create invalid combinations.
A sealed hierarchy is safer.
Conclusion
Sealed classes and enhanced enums are powerful tools for modeling application state.
They provide:
- Better type safety
- Exhaustive switching
- Clear state modeling
- Fewer invalid states
- More maintainable code
For modern Flutter applications, these features should be part of your regular Dart toolkit.






