Dart 28 🎯 Enums: Basic Enums, Enhanced Enums with Fields, Methods, and Interfaces
An enum is a type with a fixed set of named values. In most languages, that is the entire story: the values are constants, and the compiler knows all of them. Dart’s enums started that way, but Dart 2.17 changed them significantly. An enum value can now have fields, methods, and constructors. It can implement interfaces. It can carry data that differs per value. This makes Dart’s enums more than a list of constants—they are a mechanism for modeling a closed set of alternatives, each with its own data and behavior.
This chapter covers Dart’s enums in full. You will learn the basic form, the enhanced form introduced in Dart 2.17, the fields and methods that each value can have, the interfaces an enum can implement, and the exhaustive switching that enums enable. You will also see the patterns that enhanced enums replace—factory functions, lookup maps, and sealed class hierarchies—and the situations where a sealed class is still the better choice.
Key point: Every enum value is a constant instance of the enum type. In a basic enum, the values are bare names. In an enhanced enum, each value can be constructed with arguments, and the enum can declare fields, methods, and a constructor. The constructor is always private to the enum. The set of values is fixed at compile time, so a switch over an enum can be checked for exhaustiveness.
Why enums exist
The named constant problem. A program needs to represent a fixed set of options: days of the week, order statuses, log levels, payment methods. The naive approach is to use string or integer constants. 'pending', 'shipped', 'delivered'. This works until someone typos the string, or uses a value that is not in the set, or adds a new value and forgets to update the validation. An enum makes the set explicit and the compiler enforces it. A value that is not one of the declared names cannot be assigned to the type.
The exhaustiveness problem. A switch over a set of constants cannot be checked for exhaustiveness. A switch over an enum can. The compiler knows every value the enum has, so it verifies that every case is handled. Adding a new value to the enum turns every incomplete switch into a compile error, which is exactly what is needed when the set of options changes.
The data-per-value problem. Some enums carry data that differs per value. A Plan enum might have a price and a maximum number of users. A HttpStatus enum might have a code and a description. Before Dart 2.17, this required a separate map from enum value to data, or a class hierarchy with a subclass per value. Enhanced enums allow the data to be part of the enum declaration.
The behavior-per-value problem. Some enums have methods that behave differently per value. A Shape enum might have an area method that computes the area differently for each shape. Before enhanced enums, this required a switch inside every method, or a sealed class hierarchy. Enhanced enums allow each value to provide its own implementation of an abstract method.
The sealed class overlap. A sealed class with final subtypes provides the same guarantees as an enhanced enum: a closed set of alternatives, data per alternative, and exhaustive matching. The difference is that enum values are constants and the set is known at compile time, while sealed subclasses are types that can have instances. Use an enum when the alternatives are values; use a sealed class when the alternatives are types with multiple instances.
a. Basic enums
A basic enum declares a set of named values. Each value is a constant instance of the enum type.
enum Direction {
north,
south,
east,
west,
}
void main() {
var d = Direction.north;
print(d); // Direction.north
}
The values are accessed with the enum name and a dot: Direction.north. The enum type can be used in function signatures, switch statements, and type checks.
String describe(Direction d) {
switch (d) {
case Direction.north:
return 'North';
case Direction.south:
return 'South';
case Direction.east:
return 'East';
case Direction.west:
return 'West';
}
}
The switch is exhaustive. If a case is missing, the compiler reports an error. The default case is not needed—and is discouraged—because the compiler verifies that every value is handled.
Enums have two built-in properties. index is the zero-based position of the value in the declaration order. name is the value’s name as a string.
print(Direction.north.index); // 0
print(Direction.north.name); // 'north'
The values static property returns a list of all the enum values in declaration order.
print(Direction.values); // [Direction.north, Direction.south, Direction.east, Direction.west]
You can iterate the values and use them for building menus, validating input, or serializing to a list.
b. Enhanced enums: fields and methods
An enhanced enum declares fields, a constructor, and methods. Each value is constructed with arguments that populate the fields.
enum Planet {
mercury(mass: 3.303e+23, radius: 2.4397e6),
venus(mass: 4.869e+24, radius: 6.0518e6),
earth(mass: 5.976e+24, radius: 6.37814e6),
mars(mass: 6.421e+23, radius: 3.3972e6);
const Planet({required this.mass, required this.radius});
final double mass;
final double radius;
double get surfaceGravity => 6.67430e-11 * mass / (radius * radius);
}
The values are followed by a semicolon, and then the constructor, fields, and methods. The constructor is const because enum values are constants. The fields are final because the values are immutable. The method surfaceGravity computes a value from the fields.
void main() {
print(Planet.earth.surfaceGravity); // 9.8...
}
Each value has its own mass and radius, and the method computes the result for that value.
The constructor is always private. An enum cannot be instantiated from outside its declaration. The values are the only instances, and they are created at compile time.
An enhanced enum can have a non-const constructor only if the values are not constant, which is rarely useful. The standard form is const.
Fields can be of any type, including nullable types, collections, and function types. A field can have a default value in the constructor.
enum LogLevel {
debug(0, prefix: '[DEBUG]'),
info(1, prefix: '[INFO]'),
warning(2, prefix: '[WARN]'),
error(3, prefix: '[ERROR]');
const LogLevel(this.severity, {required this.prefix});
final int severity;
final String prefix;
bool shouldLog(LogLevel threshold) => severity >= threshold.severity;
}
The severity is a positional argument, and the prefix is a named argument. Each value provides its own. The shouldLog method compares the severity to a threshold.
c. Methods, interfaces, and exhaustiveness
An enhanced enum can declare methods that use its fields. It can also declare abstract methods that each value implements.
enum Shape {
circle {
@override
double area(double size) => 3.14159 * size * size;
},
square {
@override
double area(double size) => size * size;
},
triangle {
@override
double area(double size) => 0.5 * size * size;
};
const Shape();
double area(double size);
}
Each value provides its own implementation of the abstract area method. The enum declares the method as abstract, and the values implement it. This is a form of per-value behavior that does not require a switch.
The method can be called on any value:
print(Shape.circle.area(2)); // 12.566...
An enum can implement interfaces. The enum declares implements, and every value must provide the interface’s members.
abstract interface class Describable {
String describe();
}
enum Priority implements Describable {
low,
medium,
high;
@override
String describe() => 'Priority: $name';
}
The Priority enum implements Describable. The describe method uses the name property, which is available on every enum value. Any value can be used where a Describable is expected.
void printDescription(Describable d) {
print(d.describe());
}
printDescription(Priority.high); // 'Priority: high'
An enum cannot extend a class. It can implement interfaces, but it cannot inherit implementation from a superclass other than the implicit Enum class. The members that the enum needs must be declared in the enum itself or provided by the values.
The switch over an enhanced enum is exhaustive in the same way as a basic enum. The compiler knows the values, so every case must be handled. The values can carry data, and the switch can destructure it:
enum Status {
active(lastSeen: DateTime.now()),
inactive;
const Status({this.lastSeen});
final DateTime? lastSeen;
}
String describe(Status s) {
return switch (s) {
Status.active(lastSeen: var d) => 'Active since $d',
Status.inactive => 'Inactive',
};
}
The pattern Status.active(lastSeen: var d) destructures the field. The switch is exhaustive, and the field is available in the case body.
A comparison with sealed classes: a sealed class hierarchy can model alternatives that have different shapes and multiple instances. An enum can model alternatives that are singleton values with data. If the alternatives are “one of a fixed set of options,” an enum is the right choice. If they are “one of a fixed set of types, each with arbitrary instances,” a sealed class is the right choice.
Complete Example Session
// ============================================
// PART 1: BASIC ENUM
// ============================================
enum Direction {
north,
south,
east,
west,
}
void main() {
var d = Direction.north;
print(d.name); // 'north'
print(d.index); // 0
}
// ============================================
// PART 2: EXHAUSTIVE SWITCH
// ============================================
String describe(Direction d) {
return switch (d) {
Direction.north => 'North',
Direction.south => 'South',
Direction.east => 'East',
Direction.west => 'West',
};
}
// ============================================
// PART 3: VALUES AND ITERATION
// ============================================
for (var d in Direction.values) {
print(d.name);
}
// north, south, east, west
// ============================================
// PART 4: ENHANCED ENUM WITH FIELDS
// ============================================
enum Planet {
mercury(mass: 3.303e+23, radius: 2.4397e6),
venus(mass: 4.869e+24, radius: 6.0518e6),
earth(mass: 5.976e+24, radius: 6.37814e6),
mars(mass: 6.421e+23, radius: 3.3972e6);
const Planet({required this.mass, required this.radius});
final double mass;
final double radius;
double get surfaceGravity => 6.67430e-11 * mass / (radius * radius);
}
// ============================================
// PART 5: ENHANCED ENUM WITH METHODS
// ============================================
enum LogLevel {
debug(0, prefix: '[DEBUG]'),
info(1, prefix: '[INFO]'),
warning(2, prefix: '[WARN]'),
error(3, prefix: '[ERROR]');
const LogLevel(this.severity, {required this.prefix});
final int severity;
final String prefix;
bool shouldLog(LogLevel threshold) => severity >= threshold.severity;
String format(String message) => '$prefix $message';
}
// ============================================
// PART 6: PER-VALUE METHOD
// ============================================
enum Shape {
circle {
@override
double area(double size) => 3.14159 * size * size;
},
square {
@override
double area(double size) => size * size;
};
const Shape();
double area(double size);
}
// ============================================
// PART 7: ENUM IMPLEMENTING INTERFACE
// ============================================
abstract interface class Describable {
String describe();
}
enum Priority implements Describable {
low,
medium,
high;
@override
String describe() => 'Priority: $name';
}
// ============================================
// PART 8: ENUM WITH NULLABLE FIELD
// ============================================
enum Status {
active(lastSeen: DateTime.now()),
inactive;
const Status({this.lastSeen});
final DateTime? lastSeen;
}
// ============================================
// PART 9: DESTRUCTURING ENUM IN SWITCH
// ============================================
String describeStatus(Status s) {
return switch (s) {
Status.active(lastSeen: var d) => 'Active since $d',
Status.inactive => 'Inactive',
};
}
// ============================================
// PART 10: ENUM AS A MAP KEY
// ============================================
final prices = {
Priority.low: 0,
Priority.medium: 10,
Priority.high: 25,
};
print(prices[Priority.high]); // 25
The ten parts covered basic enums, exhaustive switch, values and iteration, enhanced enums with fields, enhanced enums with methods, per-value methods, enums implementing interfaces, enums with nullable fields, destructuring in switch, and enums as map keys.
Quick Reference
Basic Enum
| Feature | Syntax |
|---|---|
| Declaration | enum Name { value1, value2 } |
| Access | Name.value1 |
| Index | Name.value1.index |
| Name | Name.value1.name |
| All values | Name.values |
Enhanced Enum
| Feature | Syntax |
|---|---|
| Values with args | value(arg1, arg2) |
| Constructor | const Name(this.field) |
| Fields | final Type field; |
| Methods | Type method() { } |
| Abstract method | Type method(); |
| Per-value implementation | value { @override ... } |
Enum Rules
| Rule | Requirement |
|---|---|
| Constructor | Must be const (or factory) |
| Fields | Must be final |
| Inheritance | Cannot extend a class |
| Interfaces | Can implement |
| Mixins | Cannot use |
| Instantiation | Only the declared values |
Enum vs Sealed Class
| Aspect | Enum | Sealed Class |
|---|---|---|
| Values | Fixed constants | Types with instances |
| Data per value | Yes (enhanced) | Yes |
| Exhaustive switch | Yes | Yes |
| Multiple instances | No | Yes |
| Pattern matching | Yes | Yes |
| Use case | Fixed options | Alternative types |
Best Practices
✅ Do This:
// Use enums for fixed sets of options
enum Direction { north, south, east, west } // ✅
// Use switch for exhaustive handling
switch (d) {
case Direction.north: // ...
} // ✅
// Use enhanced enums for data per value
enum Planet {
earth(mass: 5.976e+24);
const Planet({required this.mass});
final double mass;
} // ✅
// Use per-value methods for behavior
enum Shape {
circle { double area() => ...; };
double area();
} // ✅
// Use enums as map keys
final prices = {Priority.low: 0, Priority.high: 25}; // ✅
// Implement interfaces when needed
enum Priority implements Describable { } // ✅
❌ Don’t Do This:
// Don't use string constants instead of enums
const statusPending = 'pending'; // ❌
// Don't use a default case on an exhaustive switch
switch (d) {
case Direction.north: // ...
default: // hides new values // ❌
}
// Don't make enum fields mutable
enum Bad {
value;
int count = 0; // must be final // ❌
}
// Don't try to extend an enum
class MyEnum extends Direction { } // error // ❌
// Don't instantiate an enum
var d = Direction(); // error // ❌
// Don't use an enum when values need multiple instances
// Use a sealed class instead // ⚠️
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Missing semicolon | Enhanced enum needs ; after values | Add ; before the constructor |
| Non-const constructor | Enum values are constants | Use const constructor |
| Non-final field | Enum values are immutable | Use final |
| Cannot extend enum | Enums cannot be extended | Use interfaces or sealed classes |
| Switch not exhaustive | Missing case | Handle every value |
| Default hides new values | Used default | Remove default |
| Index changes | Reordered values | Use name instead of index |
Real-World Examples
1. Order Status
enum OrderStatus {
pending,
processing,
shipped,
delivered,
cancelled,
}
2. HTTP Status with Code
enum HttpStatus {
ok(200, 'OK'),
notFound(404, 'Not Found'),
serverError(500, 'Internal Server Error');
const HttpStatus(this.code, this.message);
final int code;
final String message;
}
3. Log Level
enum LogLevel {
debug(0),
info(1),
warning(2),
error(3);
const LogLevel(this.severity);
final int severity;
}
4. Plan with Price
enum Plan {
free(price: 0, users: 1),
pro(price: 10, users: 10),
enterprise(price: 100, users: 1000);
const Plan({required this.price, required this.users});
final int price;
final int users;
}
5. Shape with Per-Value Area
enum Shape {
circle { double area(double s) => 3.14159 * s * s; },
square { double area(double s) => s * s; };
double area(double size);
}
6. Payment Method
enum PaymentMethod {
creditCard,
debitCard,
paypal,
bankTransfer,
}
7. Weekday
enum Weekday {
monday,
tuesday,
wednesday,
thursday,
friday,
saturday,
sunday;
bool get isWeekend => this == saturday || this == sunday;
}
8. Sort Order
enum SortOrder { ascending, descending }
9. Environment
enum Environment {
development(apiUrl: 'http://localhost:3000'),
staging(apiUrl: 'https://staging.example.com'),
production(apiUrl: 'https://api.example.com');
const Environment({required this.apiUrl});
final String apiUrl;
}
10. Enum to JSON
enum Status {
active,
inactive;
String toJson() => name;
static Status fromJson(String value) =>
Status.values.firstWhere((s) => s.name == value);
}
Visual
Basic vs Enhanced Enum
┌─────────────────────────────────────────────────────────────┐
│ BASIC ENUM │
│ │
│ enum Direction { │
│ north, south, east, west │
│ } │
│ │
│ Values are bare names. │
│ No data, no methods. │
│ │
├─────────────────────────────────────────────────────────────┤
│ │
│ ENHANCED ENUM │
│ │
│ enum Planet { │
│ earth(mass: 5.976e+24, radius: 6.37814e6); │
│ │
│ const Planet({required this.mass, required this.radius});│
│ │
│ final double mass; │
│ final double radius; │
│ │
│ double get surfaceGravity => ...; │
│ } │
│ │
│ Values carry data. Fields, constructor, methods. │
│ │
└─────────────────────────────────────────────────────────────┘
Exhaustive Switch
┌─────────────────────────────────────────────────────────────┐
│ enum Status { active, inactive, pending } │
│ │
│ switch (status) { │
│ case Status.active: ... │
│ case Status.inactive: ... │
│ case Status.pending: ... │
│ } │
│ ✓ Exhaustive │
│ │
│ switch (status) { │
│ case Status.active: ... │
│ case Status.inactive: ... │
│ } │
│ ✗ Error: pending not handled │
│ │
│ Adding a new value breaks all incomplete switches. │
│ │
└─────────────────────────────────────────────────────────────┘
Per-Value Methods
┌─────────────────────────────────────────────────────────────┐
│ enum Shape { │
│ circle { │
│ @override │
│ double area(double s) => 3.14159 * s * s; │
│ }, │
│ square { │
│ @override │
│ double area(double s) => s * s; │
│ }; │
│ │
│ double area(double size); │
│ } │
│ │
│ Shape.circle.area(2) → 12.566 │
│ Shape.square.area(2) → 4 │
│ │
│ Each value provides its own implementation. │
│ No switch required. │
│ │
└─────────────────────────────────────────────────────────────┘
Enum vs Sealed Class
┌─────────────────────────────────────────────────────────────┐
│ ENUM │
│ │
│ Fixed set of singleton values. │
│ enum Color { red, green, blue } │
│ │
│ Color.red is a constant. │
│ Cannot create another instance. │
│ │
├─────────────────────────────────────────────────────────────┤
│ │
│ SEALED CLASS │
│ │
│ Fixed set of types, each with instances. │
│ sealed class Shape {} │
│ final class Circle extends Shape { double radius; } │
│ │
│ Circle(2.0) and Circle(3.0) are different instances. │
│ Can create many instances of each subtype. │
│ │
└─────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Basic enum | enum Name { value1, value2 } |
| Access value | Name.value1 |
| Index | Name.value1.index |
| Name | Name.value1.name |
| All values | Name.values |
| Enhanced enum | Values with arguments |
| Constructor | const Name(this.field) |
| Fields | final Type field; |
| Methods | Regular methods |
| Abstract methods | Per-value implementation |
| Interfaces | enum Name implements Interface |
| Exhaustive switch | Yes |
Key takeaways:
- An enum is a fixed set of named values. The compiler knows every value, so a
switchover an enum is checked for exhaustiveness. Adding a new value breaks every incompleteswitchat compile time. - Every enum value has an
indexand aname. Theindexis the zero-based position in the declaration order. Thenameis the value’s identifier as a string. Thevaluesstatic property returns all values in order. - Enhanced enums carry data per value. Each value can be constructed with arguments that populate fields. The constructor is always private, and the fields are
final. The values are the only instances of the enum. - Enhanced enums can have methods and implement interfaces. A method can use the enum’s fields to compute a result. An abstract method can be implemented per value, with each value providing its own behavior.
- The
switchover an enhanced enum can destructure fields. A pattern likeStatus.active(lastSeen: var d)extracts the field and binds it to a variable in the case body. - Enums cannot be extended or instantiated. They can implement interfaces, but they cannot extend a class or use mixins. The set of values is fixed at compile time.
- Enums are ideal for fixed sets of options; sealed classes are ideal for alternative types. Use an enum when the alternatives are singleton values. Use a sealed class when the alternatives are types that can have multiple instances with different data.
Remember: Dart’s enums are more than a list of constants. An enhanced enum can carry data, define methods, and implement interfaces, while preserving the exhaustiveness guarantee that makes enums useful for pattern matching. The values are constants, the constructor is private, and the fields are final. Use enums for the fixed sets of options that appear everywhere in real programs: order statuses, log levels, payment methods, plans, and directions. Use enhanced enums when each option needs data or behavior. Use sealed classes when the alternatives are types rather than values. And use switch without a default case, so the compiler can tell you when a new value needs to be handled.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!