Deep Dive: Enhanced Enums in Flutter 3.0

Transform your cross-platform mobile apps with powerful enum patterns. Learn to bundle data and behavior together for cleaner, more maintainable Dart code.

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.

Key Enhanced Enum Capabilities

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:

CapabilityDescription
Instance VariablesAssociate data with each enum value
Methods & GettersAdd behavior that operates on enum data
Interface ImplementationImplement Comparable, and other interfaces
Custom OperatorsOverload operators for intuitive manipulation
ExtensionsAdd methods to existing enum types
MixinsShare 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.

Ready to Build Better Flutter Apps?

Our team specializes in cross-platform mobile development using Flutter and Dart. Let us help you create maintainable, performant applications with modern patterns like enhanced enums.