skip to content

In Dart, how do you declare an enhanced enum, such as an order status with a label and a colour, and what rules apply?

level: middleimportance: must knowfreq 50%

answer

  1. values call a constructor
  2. const constructor, final fields
  3. values first, then a semicolon
  4. no overriding index, == or hashCode
  5. factories return existing values only

basics

~20 s

List each value with constructor arguments, end the list with a semicolon, then declare final fields and a const constructor, plus any getters or methods. Enhanced enums, since Dart 2.17, cannot extend classes or override index, == or hashCode.

solid answer

~50 s

An enhanced enum is a class with a fixed set of constant instances. Write the values first, each calling the constructor, `pending('Pending', Color(0xFFF9A825))`, end the list with `;`, then declare `final String label; final Color color;` and `const OrderStatus(this.label, this.color);`. You can add getters such as `bool get isFinal`, methods, static members, and `implements` or `with` clauses. The rules: every instance field is `final`, including fields from mixins; every generative constructor is `const`; a factory constructor may only return one of the existing values; the enum cannot extend another class because it implicitly extends `Enum`; it cannot override `index`, `hashCode` or `==`; it cannot declare a member named `values`; and it needs at least one value. Since Dart 3.13 a primary constructor can shorten this to `enum OrderStatus(final String label, final Color color) { ... }`.

code

dart · 25 lines
dart
import 'package:flutter/material.dart';

enum OrderStatus {
  pending('Pending', Color(0xFFF9A825)),
  shipped('Shipped', Color(0xFF1565C0)),
  delivered('Delivered', Color(0xFF2E7D32)),
  cancelled('Cancelled', Color(0xFFC62828));

  const OrderStatus(this.label, this.color);

  final String label;
  final Color color;

  bool get isFinal => this == delivered || this == cancelled;

  @override
  String toString() => label;
}

void main() {
  const status = OrderStatus.shipped;
  print(status.label); // Shipped
  print(status.isFinal); // false
  print(status.index); // 1
}

go deeper

for a junior

Recall the order: values with arguments first, a semicolon, then final fields and a const constructor.

for a middle

Explain each compiler rule, final fields, const constructors, no extends, no == override, and connect it to values being shared constants.

for a senior

Decide what data belongs in the enum versus the UI layer, and keep enums used in shared Dart packages free of Flutter types.

for a principal

Weigh centralising per-value data in enums against localization, theming and package boundaries in a large codebase.

## From a list of names to a class with data A plain enum gives you names. Real apps usually need **data attached to each value**: a display label, a colour, an icon, a server code. Before Dart 2.17 that meant a parallel `switch` or `Map` somewhere else. **Enhanced enums** let the enum itself declare fields, constructors and methods, so each value carries its own data. ## Anatomy of an enhanced enum The shape is always the same: 1. **The value list comes first.** Each value is written as a constructor call, `shipped('Shipped', Color(0xFF1565C0))`, and the list ends with a **semicolon**, not a comma, when members follow. 2. **Fields** are declared next and must be `final`. 3. **A `const` generative constructor** assigns them, usually with `this.` initializing formals. 4. **Getters, methods and static members** follow, and can use `this` to refer to the current value. ## The rules the compiler enforces | Rule | Why it exists | |---|---| | Every instance field is `final`, including fields a mixin adds | values are shared constants, so they must not change | | Every generative constructor is `const` | values are compile-time constants | | A factory constructor may only return an existing value | the set of instances is closed | | No `extends`; `Enum` is the implicit superclass | enum identity and `index` come from it | | No overriding `index`, `hashCode` or `==` | equality stays identity, and `index` stays the declaration position | | No member named `values` | it would clash with the generated static list | | At least one value, declared before any other member | the value list defines the type | You **can** override `toString`, implement interfaces with `implements`, apply mixins with `with`, and add static helpers such as a lookup map. ## Using the data Because the data is part of the value, calling code no longer needs a lookup table: - `status.label` for the text in a list tile; - `status.color` for a badge or chip background; - `status.isFinal` to decide whether to show a cancel button. Adding a new status then means adding one line to the enum, with its label and colour, rather than hunting down every `switch` or `Map` that translated statuses into presentation data. ## Keeping presentation out of the domain, when it matters Putting a Flutter `Color` into the enum ties that enum to `dart:ui`. That is fine for a UI-layer enum. If the same status is shared with pure Dart code, such as a backend client package or a command-line tool, keep domain data such as a server code in the enum and derive colours in the UI layer, for example with an extension or a theme lookup keyed by the enum. Labels shown to users usually belong in localized strings rather than hard-coded English in the enum. ## The Dart 3.13 shorthand With **primary constructors**, the fields and constructor move into the header: ```dart enum OrderStatus(final String label, final Color color) { pending('Pending', Color(0xFFF9A825)), shipped('Shipped', Color(0xFF1565C0)); } ``` Primary constructors in enums are **implicitly constant**. The long form remains valid and is what most existing code uses. ## How values are created Each value in the list is a **constant expression**: the compiler evaluates `shipped('Shipped', Color(0xFF1565C0))` once, at compile time, and every reference to `OrderStatus.shipped` anywhere in the program is that same object. That is why constructor arguments must be constants themselves, such as literals, `const` constructors and other constants. A `DateTime.now()` or a value read from configuration cannot appear there. It also explains why fields must be `final`: a mutable field on a value shared by the whole program would be global mutable state hiding inside what looks like a constant. ## Common mistakes - Ending the value list with a comma and then declaring fields, which does not parse. - Declaring a non-final field to track state per value; enum values are shared singletons, so mutable state there would be global state. - Trying to override `==` to compare labels; it is forbidden, and two values are equal only if they are the same value.

  • Can an enhanced enum have a factory constructor, and what may it return?
    Yes, but it may only return one of the enum's existing values; it can never create a new instance. A typical use is `factory OrderStatus.fromLabel(String label) => values.firstWhere((s) => s.label == label, orElse: () => pending);`. A static method does the same job and is often clearer.
  • Why is overriding toString allowed when overriding == is not?
    `toString` only affects text output, so customising it cannot break the enum's guarantees. `==`, `hashCode` and `index` define identity and position; if an enum could redefine them, two different values could compare equal and collections or switches keyed on enum values would stop being reliable.

saying these in an interview costs you the question

  • Enhanced enum fields can be mutable if you need per-value state.
  • An enhanced enum can extend a base class to share fields.
  • You can override == in an enum to compare values by label.
  • A factory constructor on an enum can create new instances at runtime.
  • Enhanced enum values can be declared after the fields and methods.