Enums in Dart are a special data type that allows a variable to be a set of predefined constants. Enums are useful for representing fixed values like days of the week, months of the year, or any other set of related constants. They provide a way to define a collection of related constants in a more readable and maintainable format.
What are Enums in Dart?
Enums in Dart provide a way to define a collection of related constants or named values. Each constant in an enum type has an index value starting from zero, and they can't be modified at runtime. Enums help make the code more readable, self-documenting, and type-safe by restricting the possible values a variable can have to a predefined set.
History/Background
Enums were introduced in Dart 2.6 as a way to provide a type-safe way of representing a fixed set of values. They are commonly used in programming languages to define constants that have a clear relationship to each other. In Dart, enums are a powerful tool for creating well-structured and maintainable code.
Syntax
Enums in Dart are declared using the enum keyword followed by the enum name and a list of constant values enclosed in curly braces.
enum Days {
monday,
tuesday,
wednesday,
thursday,
friday,
saturday,
sunday
}
- The
enumkeyword is used to define an enumeration. - Each constant value inside the enum is defined as a simple identifier.
- Enums provide a way to define a set of related constants.
- Enums are type-safe, ensuring that variables can only hold values from the predefined set.
- Enums support indexing, where each constant has an integer value associated with it.
- Enums can have methods and properties defined within them.
Key Features
Example 1: Basic Usage
enum Colors {
red,
green,
blue,
yellow
}
void main() {
Colors selectedColor = Colors.blue;
print('Selected color: $selectedColor');
}
Output:
Selected color: Colors.blue
In this example, we define an enum Colors with four constant values. We then declare a variable selectedColor of type Colors and assign it the value Colors.blue. Finally, we print out the selected color.
Example 2: Enums with Methods
enum Status {
pending,
approved,
rejected
}
extension StatusExtension on Status {
String getStatusMessage() {
switch (this) {
case Status.pending:
return 'Pending approval';
case Status.approved:
return 'Approved';
case Status.rejected:
return 'Rejected';
}
}
}
void main() {
Status currentStatus = Status.approved;
print('Current status: ${currentStatus.getStatusMessage()}');
}
Output:
Current status: Approved
In this example, we define an enum Status with three constant values. We then use an extension on the enum to add a method getStatusMessage that returns a message based on the current status.
Comparison Table
| Feature | Enums | Classes |
|---|---|---|
| Definition | Define a set of constants | Define custom data structures |
| Mutability | Constants are immutable | Properties can be modified |
| Use Case | Represent fixed values | Model real-world entities |
| Indexing | Each constant has an index | No built-in indexing |
Common Mistakes to Avoid
1. Using Enums for Mutable State
Problem: Beginners often think they can change the value of an enum after it has been defined, leading to confusion about immutability.
// BAD - Don't do this
enum Color { red, green, blue }
void main() {
Color myColor = Color.red;
myColor = Color.green; // This is incorrect usage of enum.
}
Solution:
// GOOD - Do this instead
enum Color { red, green, blue }
void main() {
Color myColor = Color.red; // Correctly assigning an enum value
myColor = Color.green; // Reassigning a variable, not changing the enum
}
Why: Enums in Dart are immutable; you cannot change their defined values. Instead, you can reassign a variable to a different enum value, which is the correct approach. Always remember that enum definitions are fixed.
2. Forgetting to Use `toString` for Display
Problem: Beginners often forget that enum values do not directly translate to user-friendly strings.
// BAD - Don't do this
enum Status { active, inactive, pending }
void main() {
Status myStatus = Status.active;
print(myStatus); // Output: Status.active, not user-friendly
}
Solution:
// GOOD - Do this instead
enum Status { active, inactive, pending }
void main() {
Status myStatus = Status.active;
print(myStatus.toString().split('.').last); // Output: active
}
Why: By default, printing an enum will show the enum name along with its type, which is not user-friendly. Using toString and splitting the result allows you to get just the enum value name for display purposes.
3. Not Using Enum Values in Switch Statements
Problem: Beginners might try to use if-else statements instead of switch-case with enums, leading to less readable code.
// BAD - Don't do this
enum Direction { north, south, east, west }
void main() {
Direction dir = Direction.north;
if (dir == Direction.north) {
print("Going North");
} else if (dir == Direction.south) {
print("Going South");
}
// More conditions...
}
Solution:
// GOOD - Do this instead
enum Direction { north, south, east, west }
void main() {
Direction dir = Direction.north;
switch (dir) {
case Direction.north:
print("Going North");
break;
case Direction.south:
print("Going South");
break;
// More cases...
}
}
Why: Using a switch statement with enums makes the code cleaner and easier to read. Switch statements are also more efficient for multiple conditions compared to a series of if-else statements.
4. Overlooking the Use of Enum Methods
Problem: Beginners may not realize that enums can have methods and properties, which can lead to less functional and reusable code.
// BAD - Don't do this
enum Days { monday, tuesday, wednesday }
void main() {
Days today = Days.monday;
print(today.index); // Directly accessing index, not using methods
}
Solution:
// GOOD - Do this instead
enum Days { monday, tuesday, wednesday }
extension DaysExtension on Days {
String get name => this.toString().split('.').last;
}
void main() {
Days today = Days.monday;
print(today.name); // Using a method for better readability
}
Why: By creating extensions or methods for enums, you can encapsulate functionality that makes your code cleaner and more understandable. This also promotes reusability and separation of concerns.
5. Assuming Enums are Strings
Problem: Beginners often assume that enums are strings or can be used as strings, leading to type mismatch errors.
// BAD - Don't do this
enum Animal { cat, dog, bird }
void main() {
Animal myPet = "cat"; // This will cause a compilation error
}
Solution:
// GOOD - Do this instead
enum Animal { cat, dog, bird }
void main() {
Animal myPet = Animal.cat; // Correctly using enum
}
Why: Enums are a distinct type in Dart, and treating them as strings leads to type errors. Always use enum values correctly to avoid compilation issues.
Best Practices
1. Use Enums for Fixed Categories
Enums should be used for a fixed set of related constants. This makes your code more understandable and type-safe.
Why: Using enums helps in maintaining a clear structure and prevents the use of magic strings or numbers, which can lead to errors.
enum UserRole { admin, editor, viewer }
2. Use Extensions for Enum Functionality
Create extensions on enums to add useful methods and properties.
Why: This keeps your enum definitions clean and allows you to encapsulate behavior related to the enum.
extension UserRoleExtension on UserRole {
bool get canEdit => this == UserRole.admin || this == UserRole.editor;
}
3. Keep Enum Names Descriptive
Use clear and descriptive names for your enums and their values.
Why: Descriptive names improve code readability and help other developers understand what the enums represent.
enum PaymentStatus { pending, completed, failed } // Clear names
4. Group Related Enums Together
Place enums that are related in the same file or class.
Why: This keeps your code organized and makes it easier to manage related constants.
enum OrderStatus { pending, shipped, delivered }
enum ShipmentMethod { standard, express }
5. Avoid Complex Logic in Enums
Keep the logic in enum methods simple and avoid complex business logic.
Why: Enums should represent a state or a category, not contain complex behavior. This keeps them easy to understand and maintain.
extension ShipmentMethodExtension on ShipmentMethod {
double get cost {
switch (this) {
case ShipmentMethod.standard:
return 5.0;
case ShipmentMethod.express:
return 15.0;
}
}
}
6. Document Your Enums
Always document your enums and their values.
Why: Documentation helps other developers understand the purpose of the enum and its values, which is crucial when working in a team or maintaining code over time.
/// Represents the status of an order.
enum OrderStatus {
/// Order has been placed but not yet processed.
pending,
/// Order has been shipped to the customer.
shipped,
/// Order has been delivered to the customer.
delivered,
}
Key Points
| Point | Description |
|---|---|
| Enum Definition | Enums are a way to define a fixed set of constants, improving code readability and type safety. |
| Immutability | Enum values are immutable; you cannot change them after they are defined. |
| User-Friendly Output | Use the toString() method for human-readable output of enum values. |
| Switch Statements | Prefer switch-case statements for handling enums instead of if-else chains for better readability. |
| Extensibility | Use extensions to add functionality to enums, such as methods and properties, without cluttering your enum definition. |
| Descriptive Naming | Always use clear and descriptive names for enums and their values to enhance code clarity. |
| Avoid Magic Values | Using enums helps replace magic strings or numbers with meaningful names, reducing errors. |
| Documentation Matters | Documenting enums and their values is vital for maintaining clarity and understanding in team environments. |