On this page24 sections
- Prerequisites
- Connection With Previous Lectures
- Learning Objectives
- 1. What Is an Enhanced Enum?
- 2. Adding a Field and a const Constructor
- 3. The Semicolon After the Values
- 4. Several Fields and Named Parameters
- 5. Getters on an Enum
- 6. Methods on an Enum
- 7. Overriding toString()
- 8. Static Members
- 9. Implementing an Interface
- 10. Using a Mixin
- 11. The Rules
- 12. A Real-World Example
- Code Examples
- Common Beginner Mistakes
- Best Practices
- Mini Challenge
- Practice Questions
- Practice Answers
- Quick Revision
- Key Takeaways
- What's Next?
Lecture 68 — Enhanced Enums
| Category | 11 — Enums, Extensions & Typedefs |
| Lecture | 068 of 140 |
| Estimated video length | 11 min |
| Target | Dart 3.12.2 |
| Level | Intermediate |
A plain enum is just a list of names, so the facts about each value end up in switches spread across the program. An enhanced enum keeps those facts — fields, getters, methods — inside the enum itself.
Contents
Prerequisites · Connection · Objectives · Lesson · Code Examples · Common Mistakes · Best Practices · Mini Challenge · Practice Questions · Answers · Takeaways
Every error message in this lecture is real. Each one was produced by running the code shown on Dart 3.12.2, so you will recognize it when you meet it yourself.
Prerequisites
Students should already understand, from Lectures 052–067:
- fields, getters, methods, and
toString()overrides with@override constconstructors andfinalfields (Lecture 055)- static members (Lecture 058)
- interfaces with
implements(Lecture 063) and mixins withwith(Lecture 064) - declaring enums,
values,name,index, and exhaustiveswitch(Lecture 067)
Connection With Previous Lectures
In Lecture 067, an enum named each choice, and a switch turned a value into data:
enum CoffeeSize { small, medium, large }
int priceOf(CoffeeSize size) {
return switch (size) {
CoffeeSize.small => 250,
CoffeeSize.medium => 300,
CoffeeSize.large => 350,
};
}
void main() {
print(priceOf(CoffeeSize.medium));
}300It works, but the price lives outside the enum. Add a label, a cup volume and a calorie count, and you need three more switches in three more places. The enum knows the names; it should also know the facts.
Learning Objectives
By the end of this lecture you will be able to:
- Explain what makes an enum "enhanced"
- Give each enum value data with fields and a
constconstructor - Add getters and methods to an enum
- Override
toString()on an enum - Add static members to an enum
- Make an enum implement an interface and use a mixin
- Recognize the rules enhanced enums must follow, and their real error messages
- Model real data — sizes, planets, order statuses — with enhanced enums
1. What Is an Enhanced Enum?
An enhanced enum is an enum that also has members: fields, a constructor, getters, methods. It is still a fixed list of values — but each value can carry its own data.
Analogy — a plain enum is a list of dish names. An enhanced enum is a menu card: every dish has its price and description printed right next to its name.
2. Adding a Field and a const Constructor
Each value passes its data in parentheses. A const constructor stores it in a final
field:
enum CoffeeSize {
small(250),
medium(300),
large(350);
const CoffeeSize(this.price);
final int price;
}
void main() {
print(CoffeeSize.small.price);
print(CoffeeSize.large.price);
}250
350small(250) calls the constructor, so small.price is 250. The price now lives with the
size — no switch needed.
3. The Semicolon After the Values
When an enum has members, the list of values must come first and end with a semicolon. Forget it, and Dart reads the constructor as one more value:
enum CoffeeSize {
small(250),
medium(300),
large(350)
const CoffeeSize(this.price);
final int price;
}Error: Expected '}' before this.
const CoffeeSize(this.price);
^^^^^A plain enum like enum CoffeeSize { small, medium, large } needs no semicolon, because
nothing follows the values.
4. Several Fields and Named Parameters
An enum can have as many fields as it needs. Named parameters keep the values readable:
enum Planet {
mercury(mass: 3.30e23, radius: 2.44e6),
earth(mass: 5.97e24, radius: 6.37e6),
jupiter(mass: 1.90e27, radius: 6.99e7);
const Planet({required this.mass, required this.radius});
final double mass;
final double radius;
}
void main() {
for (var planet in Planet.values) {
print('${planet.name}: radius ${planet.radius} m');
}
}mercury: radius 2440000.0 m
earth: radius 6370000.0 m
jupiter: radius 69900000.0 mvalues, name and index from Lecture 067 all still work.
5. Getters on an Enum
A getter computes something from the value's fields — or from the value itself through
this:
enum CoffeeSize {
small(250, 'S'),
medium(300, 'M'),
large(350, 'L');
const CoffeeSize(this.price, this.code);
final int price;
final String code;
bool get isLarge => this == CoffeeSize.large;
String get label => '$code - Rs $price';
}
void main() {
print(CoffeeSize.medium.label);
print(CoffeeSize.medium.isLarge);
print(CoffeeSize.large.isLarge);
}M - Rs 300
false
true6. Methods on an Enum
Methods work as in a class. Inside the enum, switch (this) picks behaviour per value —
and it is still exhaustive:
enum CoffeeSize {
small(250),
medium(300),
large(350);
const CoffeeSize(this.price);
final int price;
int total(int cups) => price * cups;
int get extraShots => switch (this) {
CoffeeSize.small => 0,
CoffeeSize.medium => 1,
CoffeeSize.large => 2,
};
}
void main() {
print(CoffeeSize.small.total(3));
print(CoffeeSize.large.extraShots);
}750
2The switch from the Connection section has moved inside the enum, where it belongs.
7. Overriding toString()
By default, printing an enum value shows CoffeeSize.medium. Override toString() to
choose the text:
enum CoffeeSize {
small(250),
medium(300),
large(350);
const CoffeeSize(this.price);
final int price;
@override
String toString() => '${name.toUpperCase()} (Rs $price)';
}
void main() {
print(CoffeeSize.medium);
print('Order: ${CoffeeSize.large}');
}MEDIUM (Rs 300)
Order: LARGE (Rs 350)8. Static Members
Static fields and methods belong to the enum as a whole. Inside the enum, values and the
value names can be used directly:
enum CoffeeSize {
small(250),
medium(300),
large(350);
const CoffeeSize(this.price);
final int price;
static const CoffeeSize standard = medium;
static CoffeeSize bestFor(int budget) {
var best = small;
for (var size in values) {
if (size.price <= budget) {
best = size;
}
}
return best;
}
}
void main() {
print(CoffeeSize.standard.price);
print(CoffeeSize.bestFor(320).name);
print(CoffeeSize.bestFor(400).name);
}300
medium
largebestFor is a lookup: it turns a budget into the right value.
9. Implementing an Interface
An enum can implement an interface, so its values fit anywhere that interface is expected:
abstract class Describable {
String describe();
}
enum CoffeeSize implements Describable {
small(250),
medium(300),
large(350);
const CoffeeSize(this.price);
final int price;
@override
String describe() => 'A $name coffee costs Rs $price';
}
void printAll(List<Describable> items) {
for (var item in items) {
print(item.describe());
}
}
void main() {
printAll(CoffeeSize.values);
}A small coffee costs Rs 250
A medium coffee costs Rs 300
A large coffee costs Rs 350printAll knows nothing about coffee — only that each item can describe() itself.
10. Using a Mixin
An enum can also mix in shared behaviour with with:
mixin Announcer {
void announce() => print('Now serving: $this');
}
enum CoffeeSize with Announcer { small, medium, large }
void main() {
CoffeeSize.large.announce();
}Now serving: CoffeeSize.largeAn enum cannot use extends — every enum already has a built-in parent, Enum. Writing
enum CoffeeSize extends Drink gives Error: Unexpected tokens.
11. The Rules
Enum values are created once, by Dart, as constants. That is why:
- Every field must be
final. - The constructor must be
const. - You cannot call the constructor yourself — the values listed are the only ones.
enum CoffeeSize {
small(250),
medium(300),
large(350);
const CoffeeSize(this.price);
final int price;
}
void main() {
var huge = CoffeeSize(500);
print(huge);
}Error: Enums can't be instantiated.
var huge = CoffeeSize(500);
^^^^^^^^^^Breaking rules 1 and 2 is shown in Common Beginner Mistakes.
12. A Real-World Example
Sara's coffee order moves through fixed stages. Each stage has a label, knows whether it is the last one, and knows what comes next:
enum OrderStatus {
placed('Order placed'),
preparing('Being prepared'),
ready('Ready for pickup'),
collected('Collected');
const OrderStatus(this.label);
final String label;
static const OrderStatus first = placed;
bool get isFinished => this == OrderStatus.collected;
OrderStatus next() => isFinished ? this : values[index + 1];
@override
String toString() => '${index + 1}. $label';
}
void main() {
var status = OrderStatus.first;
print("Sara's order:");
while (!status.isFinished) {
print(status);
status = status.next();
}
print(status);
}Sara's order:
1. Order placed
2. Being prepared
3. Ready for pickup
4. CollectedFields, a getter, a method, a static constant and toString() work together. To add a stage,
you add one line to the list — nothing else in the program changes.
Code Examples
Example 1 — One Field
enum TrafficLight {
red(30),
yellow(5),
green(25);
const TrafficLight(this.seconds);
final int seconds;
}
void main() {
for (var light in TrafficLight.values) {
print('${light.name}: ${light.seconds}s');
}
}red: 30s
yellow: 5s
green: 25sExample 2 — A Computed Getter
enum Planet {
mercury(mass: 3.30e23, radius: 2.44e6),
earth(mass: 5.97e24, radius: 6.37e6);
const Planet({required this.mass, required this.radius});
final double mass;
final double radius;
static const double g = 6.674e-11;
double get gravity => g * mass / (radius * radius);
}
void main() {
for (var planet in Planet.values) {
print('${planet.name}: ${planet.gravity.toStringAsFixed(1)} m/s²');
}
}mercury: 3.7 m/s²
earth: 9.8 m/s²Example 3 — A bool Getter
enum Weekday {
monday,
saturday,
sunday;
bool get isWeekend => this == saturday || this == sunday;
}
void main() {
print(Weekday.monday.isWeekend);
print(Weekday.sunday.isWeekend);
}false
trueAn enum can have members without any fields at all.
Example 4 — A Method with a Parameter
enum Currency {
usd(280.0),
eur(300.0);
const Currency(this.rateInRupees);
final double rateInRupees;
double toRupees(double amount) => amount * rateInRupees;
}
void main() {
print(Currency.usd.toRupees(10));
print(Currency.eur.toRupees(2.5));
}2800.0
750.0Example 5 — Overriding toString()
enum Medal {
gold('🥇'),
silver('🥈'),
bronze('🥉');
const Medal(this.icon);
final String icon;
@override
String toString() => '$icon $name';
}
void main() {
print(Medal.gold);
print('Zayan won ${Medal.silver}');
}🥇 gold
Zayan won 🥈 silverExample 6 — Implementing an Interface
abstract class Priced {
int get price;
}
enum Ticket implements Priced {
adult(500),
child(250);
const Ticket(this.price);
@override
final int price;
}
void main() {
Priced ticket = Ticket.child;
print(ticket.price);
}250A final field satisfies the interface's price getter.
Example 7 — Student Marks
enum Grade {
a(80, 'Excellent'),
b(60, 'Good'),
c(40, 'Pass'),
f(0, 'Fail');
const Grade(this.minMarks, this.label);
final int minMarks;
final String label;
static Grade fromMarks(int marks) {
for (var grade in values) {
if (marks >= grade.minMarks) {
return grade;
}
}
return f;
}
}
void main() {
var marks = {'Ali': 85, 'Sara': 64, 'Zayan': 38};
marks.forEach((student, score) {
var grade = Grade.fromMarks(score);
print('$student: ${grade.name.toUpperCase()} (${grade.label})');
});
}Ali: A (Excellent)
Sara: B (Good)
Zayan: F (Fail)fromMarks checks the grades from highest to lowest — the order they are listed in.
Example 8 — Shopping Total
enum CoffeeSize {
small(250),
medium(300),
large(350);
const CoffeeSize(this.price);
final int price;
int total(int cups) => price * cups;
}
void main() {
var bill = CoffeeSize.large.total(2) + CoffeeSize.small.total(1);
print('Total: Rs $bill');
}Total: Rs 950Example 9 — Bank Balance
enum AccountType {
basic(0.02),
premium(0.05);
const AccountType(this.interestRate);
final double interestRate;
double yearlyInterest(double balance) => balance * interestRate;
}
void main() {
var balance = 10000.0;
for (var type in AccountType.values) {
print('${type.name}: ${type.yearlyInterest(balance)}');
}
}basic: 200.0
premium: 500.0Example 10 — Temperature Units
enum TempUnit {
celsius('°C'),
fahrenheit('°F');
const TempUnit(this.symbol);
final String symbol;
double fromCelsius(double c) => switch (this) {
TempUnit.celsius => c,
TempUnit.fahrenheit => c * 9 / 5 + 32,
};
String show(double c) => '${fromCelsius(c)}$symbol';
}
void main() {
print(TempUnit.celsius.show(25));
print(TempUnit.fahrenheit.show(25));
}25.0°C
77.0°FCommon Beginner Mistakes
1. Forgetting the semicolon after the last value
enum CoffeeSize {
small(250),
medium(300),
// large(350) // ✗ no semicolon
large(350);
const CoffeeSize(this.price);
final int price;
}Error: Expected '}' before this.Values come first and end with ; whenever members follow.
2. A field that is not final
enum CoffeeSize {
small(250),
medium(300),
large(350);
const CoffeeSize(this.price);
// int price; // ✗
final int price;
}Error: Enum constructors are constant so all fields must be final.3. A constructor without const
enum CoffeeSize {
small(250),
medium(300),
large(350);
// CoffeeSize(this.price); // ✗
const CoffeeSize(this.price);
final int price;
}Error: Generative enum constructors must be marked as 'const'.4. Trying to create a new value
void main() {
// var huge = CoffeeSize(500); // ✗
}Error: Enums can't be instantiated.Add the value to the enum's list instead: huge(500),.
5. Trying to change a value's data
void main() {
// CoffeeSize.small.price = 200; // ✗
}Error: The setter 'price' isn't defined for the type 'CoffeeSize'.Enum data is fixed. If it must change while the program runs, it belongs in a class.
Best Practices
- Put data that belongs to a value in the enum, instead of in switches elsewhere.
- Use named parameters when a value has two or more fields of the same type.
- Use a getter for anything you can compute from the fields.
- Write
switch (this)inside the enum when behaviour differs per value. - Override
toString()when the defaultCoffeeSize.smallis not what users should see. - Use an enum only for a fixed set; use a class when users can create new items.
Mini Challenge
Zayan's pizza shop sells two sizes: small with 4 slices for Rs 800, and large with
8 slices for Rs 1400. Write an enhanced enum with both fields, a getter pricePerSlice
(an int, using ~/), and a toString() override, then print every size.
Your program should print exactly:
small: 4 slices, Rs 200 each
large: 8 slices, Rs 175 eachTry it yourself before opening the solution.
Show solution
enum PizzaSize {
small(slices: 4, price: 800),
large(slices: 8, price: 1400);
const PizzaSize({required this.slices, required this.price});
final int slices;
final int price;
int get pricePerSlice => price ~/ slices;
@override
String toString() => '$name: $slices slices, Rs $pricePerSlice each';
}
void main() {
for (var size in PizzaSize.values) {
print(size);
}
}small: 4 slices, Rs 200 each
large: 8 slices, Rs 175 eachHow it works
- Named parameters make it clear which number is slices and which is price.
pricePerSliceis computed:800 ~/ 4is200,1400 ~/ 8is175.print(size)calls the overriddentoString().
Practice Questions
Work through these in order — they run from beginner to application to challenge. Every question gives you exact values, so you can compare your output with the answers.
Beginner
- Create an enum
Drinkwithtea(100)andcoffee(250), stored in a fieldprice. Print the price of coffee. - Create an enum
Sizewith valuess,m,land aStringfieldlabel('Small','Medium','Large'). Loop overvaluesand print each label. - Add a
toString()override toDrinkfrom question 1 so thatprint(Drink.tea)printstea - Rs 100.
Application
-
Create an enum
Fruitwithapple(52)andbanana(89)(calories). Add a getterisHighCaloriethat istrueabove80. Print it for both fruits. -
Create an enum
Ticketwithadult(500)andchild(250)and a methodcost(int count). PrintTicket.adult.cost(3)andTicket.child.cost(2). -
Add a static method
cheapest()toTicketthat returns the value with the lowest price. Print its name. -
This enum does not compile. Find the two mistakes and fix them so it prints
5:enum Rating { low(1), high(5) const Rating(this.stars); int stars; } void main() { print(Rating.high.stars); } -
Write an interface
Labeledwith a methodString label(). Make an enumRoomwithkitchenandbedroomimplement it, returning'Room: kitchen'and so on. Print the label of every room.
Challenge
- Create an enum
TrafficLightwithred,green,yellowand a methodnext()that goes red → green → yellow → red. Starting atred, print five lights in a row. - Create an enum
AccountTypewithbasic(minDeposit: 500, rate: 0.02)andpremium(minDeposit: 5000, rate: 0.05). Addbool canOpen(double deposit). Ali has3000.0: print each type withtrueorfalse.
Practice Answers
Question 1 — Solution
enum Drink {
tea(100),
coffee(250);
const Drink(this.price);
final int price;
}
void main() {
print(Drink.coffee.price);
}250Question 2 — Solution
enum Size {
s('Small'),
m('Medium'),
l('Large');
const Size(this.label);
final String label;
}
void main() {
for (var size in Size.values) {
print(size.label);
}
}Small
Medium
LargeQuestion 3 — Solution
enum Drink {
tea(100),
coffee(250);
const Drink(this.price);
final int price;
@override
String toString() => '$name - Rs $price';
}
void main() {
print(Drink.tea);
}tea - Rs 100Question 4 — Solution
enum Fruit {
apple(52),
banana(89);
const Fruit(this.calories);
final int calories;
bool get isHighCalorie => calories > 80;
}
void main() {
print(Fruit.apple.isHighCalorie);
print(Fruit.banana.isHighCalorie);
}false
trueQuestion 5 — Solution
enum Ticket {
adult(500),
child(250);
const Ticket(this.price);
final int price;
int cost(int count) => price * count;
}
void main() {
print(Ticket.adult.cost(3));
print(Ticket.child.cost(2));
}1500
500Question 6 — Solution
enum Ticket {
adult(500),
child(250);
const Ticket(this.price);
final int price;
static Ticket cheapest() {
var best = values.first;
for (var ticket in values) {
if (ticket.price < best.price) {
best = ticket;
}
}
return best;
}
}
void main() {
print(Ticket.cheapest().name);
}childQuestion 7 — Solution
The last value needs a semicolon, and the field must be final:
enum Rating {
low(1),
high(5);
const Rating(this.stars);
final int stars;
}
void main() {
print(Rating.high.stars);
}5Question 8 — Solution
abstract class Labeled {
String label();
}
enum Room implements Labeled {
kitchen,
bedroom;
@override
String label() => 'Room: $name';
}
void main() {
for (var room in Room.values) {
print(room.label());
}
}Room: kitchen
Room: bedroomQuestion 9 — Solution
enum TrafficLight {
red,
green,
yellow;
TrafficLight next() => values[(index + 1) % values.length];
}
void main() {
var light = TrafficLight.red;
for (var i = 0; i < 5; i++) {
print(light.name);
light = light.next();
}
}red
green
yellow
red
green% values.length wraps the index from 2 back to 0.
Question 10 — Solution
enum AccountType {
basic(minDeposit: 500, rate: 0.02),
premium(minDeposit: 5000, rate: 0.05);
const AccountType({required this.minDeposit, required this.rate});
final double minDeposit;
final double rate;
bool canOpen(double deposit) => deposit >= minDeposit;
}
void main() {
var deposit = 3000.0;
for (var type in AccountType.values) {
print('${type.name}: ${type.canOpen(deposit)}');
}
}basic: true
premium: falseQuick Revision
- What makes an enum "enhanced"?
- Where must the list of values go, and how must it end?
- Why must every enum field be
final? - What error do you get when you call an enum's constructor yourself?
- How does a method inside an enum choose behaviour per value?
- What does printing an enum value show if you do not override
toString()? - Which can an enum use —
extends,implements,with?
Key Takeaways
- An enhanced enum adds fields, a constructor, getters and methods to a fixed set of values.
- Each value passes its data to a
constconstructor:small(250). - Values come first and end with a semicolon when members follow.
- All fields are
final; values are constants created by Dart, never by you. - Getters and methods work as in classes;
switch (this)picks behaviour per value. - Override
toString()to control how a value prints. - Static members,
implementsandwithall work;extendsdoes not. - Keep each value's data inside the enum, so adding a value means adding one line.
What's Next?
Lecture 69 — Extension Methods. Today we added methods to enums we wrote ourselves. But
what about types we did not write — could String get a capitalize() method, or int an
isPrime getter of our own? Next we will learn how extension methods add new members to
existing types without touching their source code.