What Are Enhanced Enums?
Enhanced enums were introduced in Dart 2.17 and fully supported in Flutter 3.0, transforming a simple language feature into a powerful tool for mobile developers. Unlike traditional enums that only store names, enhanced enums allow you to associate fields, methods, and constant constructors with each enum value.
This guide explores how enhanced enums can improve your cross-platform mobile apps by bundling related data and behavior together, reducing boilerplate, and making your code more expressive and maintainable. Whether you're building with Flutter or React Native, these patterns help create cleaner, more maintainable applications that scale effectively across iOS and Android platforms.
Instance Variables
Associate data with each enum value using final fields and constant constructors
Methods & Getters
Add instance methods and computed properties that operate on enum data
Interface Implementation
Implement interfaces like Comparable for sorting and comparison operations
Custom Operators
Overload operators for intuitive enum value manipulation
Traditional Enums
Traditional enums in Dart are simple collections of named constants:
enum Priority { low, medium, high }
While useful for basic type safety, traditional enums cannot associate additional data or behavior with each value.
Enhanced Enums
Enhanced enums extend this capability by allowing constructors, fields, and methods:
enum Priority {
low(color: Colors.green),
medium(color: Colors.yellow),
high(color: Colors.red);
final Color color;
const Priority({required this.color});
}
Now each priority has an associated color value.
Declaring Enhanced Enums
Enhanced enums in Dart must follow specific constraints to ensure type safety and compile-time optimization:
Core Requirements
- Instance variables must be final, including those added by mixins
- All generative constructors must be constant
- Cannot override
index,hashCode, or the equality operator== - Cannot declare a member named
values- this conflicts with the auto-generated static getter - All instances must be declared at the beginning of the declaration
Basic Enhanced Enum with Constructor
enum Day {
monday("Mon"),
tuesday("Tue"),
wednesday("Wed"),
thursday("Thu"),
friday("Fri"),
saturday("Sat"),
sunday("Sun");
const Day(this.abbreviation);
final String abbreviation;
}
// Usage
Day today = Day.monday;
print(today.abbreviation); // "Mon"
Implementing Interfaces
Enhanced enums can implement interfaces like Comparable, enabling powerful patterns for sorting and comparison:
enum Vehicle implements Comparable<Vehicle> {
car(tires: 4, passengers: 5, carbonPerKilometer: 400),
bus(tires: 6, passengers: 50, carbonPerKilometer: 800),
bicycle(tires: 2, passengers: 1, carbonPerKilometer: 0);
const Vehicle({
required this.tires,
required this.passengers,
required this.carbonPerKilometer,
});
final int tires;
final int passengers;
final int carbonPerKilometer;
int get carbonFootprint =>
(carbonPerKilometer / passengers).round();
bool get isTwoWheeled => this == Vehicle.bicycle;
@override
int compareTo(Vehicle other) =>
carbonFootprint - other.carbonFootprint;
}
This implementation allows sorting vehicles by their carbon footprint and enables natural comparison operations. By implementing interfaces, your enums become more versatile and can integrate seamlessly with Dart's standard library collection methods.
Methods and Getters in Enhanced Enums
Instance methods in enhanced enums can use this to reference the current enum value, enabling sophisticated behavior:
Instance Methods
enum OrderStatus {
created("Order Created"),
shipped("Order Shipped"),
delivered("Order Delivered");
const OrderStatus(this.message);
final String message;
String getLogMessage() =>
"Order status changed to $message";
}
// Usage
OrderStatus status = OrderStatus.shipped;
print(status.getLogMessage());
// Output: "Order status changed to Order Shipped"
Getters for Computed Properties
enum GameHatGPIO {
selectButton(7),
leftButton(12),
rightButton(16);
const GameHatGPIO(this.pin);
final int pin;
bool get isLeftSide => pin < 10;
bool get isRightSide => pin >= 10;
}
These patterns are particularly valuable when building mobile applications that require clean, maintainable state management and configuration handling.
Advanced Patterns
Custom Operators
Enhanced enums support custom operators for intuitive manipulation:
enum Month {
january("Jan"), february("Feb"), march("Mar"),
april("Apr"), may("May"), june("Jun"),
july("Jul"), august("Aug"), september("Sep"),
october("Oct"), november("Nov"), december("Dec");
const Month(this.abbreviation);
final String abbreviation;
Month operator +(int other) {
int result = (this.index + other) % 12;
return Month.values[result];
}
}
// Usage
Month current = Month.january;
Month next = current + 1; // february
Extensions with Enums
Extensions add methods to enums you don't control:
enum LogLevel { debug, info, warn, error }
extension LogLevelExtension on LogLevel {
String formattedString(String message) {
final labels = {
LogLevel.debug: "[DEBUG]",
LogLevel.info: "[INFO]",
LogLevel.warn: "[WARN]",
LogLevel.error: "[ERROR]",
};
return "${labels[this]} $message";
}
}
// Usage
LogLevel.WARN.formattedString("Something went wrong");
// Returns: "[WARN] Something went wrong"
These advanced patterns enable you to build sophisticated web and mobile applications with clean, expressive APIs.
Mixins with Enhanced Enums
Mixins enable shared functionality across different enums:
mixin Loggable {
String getLogMessage();
}
enum OrderStatus with Loggable {
created("Order Created"),
shipped("Order Shipped"),
delivered("Order Delivered");
const OrderStatus(this.message);
final String message;
@override
String getLogMessage() =>
"Order status changed to $message";
}
enum PaymentStatus with Loggable {
pending("Payment Pending"),
completed("Payment Completed"),
failed("Payment Failed");
const PaymentStatus(this.message);
final String message;
@override
String getLogMessage() => "Payment status: $message";
}
Key difference: Mixins can access private members of the class they mix into, while extensions cannot access private members of the extended type. This makes mixins ideal for sharing implementation details across related enum types in your application.
Using Enhanced Enums in Flutter Apps
Enhanced enums are particularly valuable when building cross-platform mobile applications with Flutter. They help organize code, reduce boilerplate, and make your app architecture more maintainable. Combined with AI-powered features, these patterns enable you to build intelligent applications that adapt to user behavior.
UI State Management
enum AsyncState<T> {
idle,
loading,
success(T data),
error(String message);
bool get isLoading => this == AsyncState.loading;
bool get isSuccess => this == AsyncState.success;
bool get isError => this == AsyncState.error;
T? get data => switch (this) {
AsyncState.success(T d) => d,
_ => null,
};
}
Theme Configuration
enum AppTheme {
light(brightness: Brightness.light, primary: Colors.blue),
dark(brightness: Brightness.dark, primary: Colors.blueAccent),
highContrast(brightness: Brightness.dark, primary: Colors.yellow);
const AppTheme({
required this.brightness,
required this.primary,
});
final Brightness brightness;
final Color primary;
ThemeData get themeData => ThemeData(
brightness: brightness,
primaryColor: primary,
useMaterial3: true,
);
}
Route Configuration
enum AppRoute {
home('/'),
profile('/profile'),
settings('/settings'),
details('/details/:id');
const AppRoute(this.path);
final String path;
String getPath(String? id) =>
path.replaceAll(':id', id ?? '');
static AppRoute fromPath(String path) =>
values.firstWhere((r) => r.path == path ||
path.startsWith(r.path.replaceAll(':id', '')));
}
Best Practices
Naming Conventions
- Use PascalCase for enum names
- Use camelCase for enum values
- Use trailing commas to prevent copy-paste errors
// ✓ Correct
enum ColorScheme { primary, secondary, surface, error }
// ✗ Avoid
enum colors { red, green, blue }
Persistence Guidelines
Never persist enum values as integers. Always store and retrieve as strings for clarity:
// ✗ Avoid - integers lack context
database.insert('status', 1);
// ✓ Prefer - strings are explicit
database.insert('status', OrderStatus.shipped.name);
// Safe loading with factory constructor
factory OrderStatus.fromString(String value) {
try {
return values.byName(value);
} catch (e) {
return OrderStatus.created; // Default fallback
}
}
When Not to Use Enums
Not all constants belong in enums. Group only related, fixed sets of values:
// ✗ Avoid - unrelated constants
enum Basic {
font,
weight,
size,
}
// ✓ Prefer - separate concerns
enum FontWeight { normal, bold, italic }
enum FontSize { small, medium, large }
Following these best practices ensures your code remains maintainable as your mobile application grows in complexity.
Summary
Enhanced enums in Dart 3.0 and Flutter provide powerful capabilities for cross-platform mobile development:
| Capability | Description |
|---|---|
| Instance Variables | Associate data with each enum value |
| Methods & Getters | Add behavior that operates on enum data |
| Interface Implementation | Implement Comparable, and other interfaces |
| Custom Operators | Overload operators for intuitive manipulation |
| Extensions | Add methods to existing enum types |
| Mixins | Share functionality across different enums |
By bundling related data and behavior together, enhanced enums help create more expressive, maintainable code for your mobile applications. Use them for UI state management, theme configurations, route definitions, and any scenario where you need type-safe, fixed sets of values with associated behavior.
Explore more Flutter development resources to build better cross-platform applications, or learn how our mobile development team can help you implement these patterns in your next project.
Frequently Asked Questions
What Dart version introduced enhanced enums?
Enhanced enums were introduced in Dart 2.17 and fully supported in Flutter 3.0. Ensure your pubspec SDK constraint includes at least '2.17.0'.
Can enhanced enums extend other classes?
No. Enhanced enums automatically extend the Enum class and cannot extend any other class. However, they can implement interfaces like Comparable.
How do I iterate over all enum values?
Use the automatically generated `values` getter: `YourEnum.values.forEach((value) { ... })`. Note that you cannot declare a member named 'values' in your enum.
Can I override the index property in an enhanced enum?
No. The index property, hashCode, and equality operator are automatically generated and cannot be overridden in enhanced enums.
What's the difference between mixins and extensions with enums?
Mixins can access private members of the class they mix into, while extensions cannot access private members. Use mixins for shared functionality, extensions for adding methods to types you don't control.