Dart (3.13) Module 7 – Classes, constructors and enums
Enhanced enums and dot shorthands
Declare Dart enums with fields and methods, look values up without exceptions, switch over them with no case missing, and drop type names with dot shorthands.
What you will learn
- Write a lookup that turns text into an enum value, or null, without throwing
- Declare an enhanced enum with a const constructor, fields, getters and an interface
- Write a switch over an enum that the compiler checks for every value
- Use dot shorthands for enum values, static members and constructors
- Explain where a dot shorthand has no context type and fails to compile
Before you start
What you’ll learn · Before you start
What you will learn
- Write a lookup that turns text into an enum value, or null, without throwing
- Declare an enhanced enum with a const constructor, fields, getters and an interface
- Write a switch over an enum that the compiler checks for every value
- Use dot shorthands for enum values, static members and constructors
- Explain where a dot shorthand has no context type and fails to compile
Before you start
On this page
A support desk keeps each ticket’s priority in a String, and one misspelt value, ‘urgnt’, is accepted by every function that takes a String. An enum limits the priority to four values that the compiler knows by name, so a misspelt one never compiles, and a switch over the four can be checked for a missing case.
You will look a value up from text without an exception, give every value of an enum its own data and methods, write switches that the compiler checks for every value, and use Dart’s dot shorthands to leave out type names that the surrounding code already supplies.
An enum is a closed set of names, and byName throws on a stranger
lookup.dart declares the four priorities of the desk and asks the enum about itself:
enum Priority { low, normal, high, urgent }
void main() {
print(Priority.high);
print('${Priority.high.name} is number ${Priority.high.index}');
print(Priority.values);
print(Priority.values.byName('urgent'));
try {
Priority.values.byName('unknown');
} on ArgumentError catch (e) {
print(e);
}
final byName = Priority.values.asNameMap();
print(byName['urgent']);
print(byName['unknown']);
} Output
Priority.high high is number 2 [Priority.low, Priority.normal, Priority.high, Priority.urgent] Priority.urgent Invalid argument (name): No enum value with that name: "unknown" Priority.urgent null
Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run lookup.dart
An enum: A special kind of class with a fixed number of constant values, declared with enum. Each value is one shared object with a name and an index, and Priority.values lists them in the order written. is a special kind of class with a fixed number of constant values, as the enums page
describes it. Priority.high printed with its type in front, name gave the bare word and index the zero-based place
of the value in the declaration, 2. Priority.values is the list of all four, in the order they were written, and that
order is the only thing the index records.
The last lines are the point of this section. values.byName('urgent') found its value, and byName('unknown') threw
an ArgumentError whose text names the argument and quotes the stranger. The API page for the extension explains that
byName walks through the values one by one, and it suggests a map from asNameMap() when lookups are frequent. The map
gives a second benefit: a name that is not there reads as null, an ordinary result instead of a crash. Step through the
two lookups on the same text:
The drawing shows the whole scene; its 4 steps are listed below.
Every step as text
- Text arrives from outside. A form field, a file or a request hands the program the text "unknown". Nothing has checked it against the four priority names yet, so the program does not know whether a value exists for it.
- byName looks, then throws. values.byName walks through the values, compares each name with the text and, when none matches, throws. The recorded message was Invalid argument (name), then No enum value with that name, then the text in quotes.
- A map answers with null. asNameMap builds a map from each name to its value once. A key that is not in the map reads as null, which is an ordinary result the caller can test, so the lookup with unknown printed null and went on.
- Choose by who typed the text. byName fits a name that your own code wrote and that must exist. For text from a user, a file or a network call, ask the map and decide what null means, such as a default priority or an error message to the user.
Use byName when your own code wrote the name and the value must exist. For text that arrives from a form, a file or a
network call, ask the map and decide what null means for the caller.
Handing user text to byName
Priority.values.byName(form['priority']) stops the whole request when someone types ‘urgnt’. Write
Priority.values.asNameMap()[text] instead, and answer a null with a message that lists the four valid names.
Checkpoint
Check: Looking an enum value up by name
An enhanced enum gives every value its own data and methods
The desk also needs to know how fast each priority must be answered. priority.dart attaches that number to the value itself, together with a getter, a method, a lookup and an interface:
enum Priority implements Comparable<Priority> {
low(hoursToReply: 72),
normal(hoursToReply: 24),
high(hoursToReply: 4),
urgent(hoursToReply: 1);
const Priority({required this.hoursToReply});
final int hoursToReply;
bool get pagesOnCall => this == urgent;
Priority get next => this == urgent ? urgent : values[index + 1];
static Priority? tryParse(String text) =>
values.asNameMap()[text.trim().toLowerCase()];
@override
int compareTo(Priority other) => other.hoursToReply - hoursToReply;
}
void main() {
for (final p in Priority.values) {
print('${p.name}: reply in ${p.hoursToReply} h, pages on call ${p.pagesOnCall}');
}
print(Priority.normal.next);
print(Priority.urgent.next);
print(Priority.tryParse(' High '));
print(Priority.tryParse('critical'));
final shuffled = [Priority.urgent, Priority.low, Priority.high, Priority.normal];
print(shuffled..sort());
} Output
low: reply in 72 h, pages on call false normal: reply in 24 h, pages on call false high: reply in 4 h, pages on call false urgent: reply in 1 h, pages on call true Priority.high Priority.urgent Priority.high null [Priority.low, Priority.normal, Priority.high, Priority.urgent]
Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run priority.dart
Each entry before the semicolon, such as high(hoursToReply: 4), is a call to the const constructor, so the declaration
builds exactly four objects and no code can make a fifth. An enum with members like these is an
enhanced enum: An enum that has final fields, a const constructor, methods, getters or implemented interfaces, available since Dart 2.17. It still has a fixed set of instances and cannot be extended., available since Dart 2.17. Step through how the constants come to exist:
The drawing shows the whole scene; its 4 steps are listed below.
Every step as text
- The declaration lists calls. Each entry before the semicolon, such as low(hoursToReply: 72), calls the const constructor with its arguments. The entries come first and the members of the class follow the semicolon.
- One object per entry. The four calls build four objects, each with its own final hoursToReply. They are built once and shared, so Priority.high is the same object wherever it is written, and no other code can build a fifth.
- values keeps the order. Priority.values is a list of the four objects in the order written, and each object's index is its place in that list, from 0 for low to 3 for urgent.
- next reads the next place. The getter next returns values[index + 1], so normal gives high. The last constant has no next place, and the getter returns urgent itself, as the recorded run printed for Priority.urgent.next.
The getter pagesOnCall compares this with urgent, and next reads values[index + 1], so normal.next printed
Priority.high. The static method tryParse is the safe lookup of the previous section packed into the type, where
every caller finds it: it trimmed and lower-cased ’ High ’ and returned null for ‘critical’. Because the enum implements
Comparable<Priority>, a plain sort() put the shuffled list back into the order of the declaration.
Key idea
An enhanced enum is a class with a fixed number of const instances. Whatever differs from one value to the next, such as a reply time, belongs in a final field of the enum, so the rule lives next to the value and cannot drift away from it.
The enums page lists what an enhanced enum may and may not do. These are the rules that decide most designs:
| Rule on the enums page | What it means for the desk |
|---|---|
| Every instance variable is final | hoursToReply can never change after the constant exists |
| Every generative constructor is const | the constants are built before the program runs |
| All instances come first, with at least one | the entries end with a semicolon before any field or method |
| No other class can be extended | an enum always extends Enum and nothing else |
index, hashCode and == cannot be overridden |
two enum values are equal only when they are the same constant |
No member may be called values |
the generated values list is always yours to rely on |
The rule about inheritance can be seen failing. extend_enum.dart tries to subclass Priority:
enum Priority { low, high }
class Rush extends Priority {}
void main() {} Output (exit status 254)
Printed as an error (standard error)
extend_enum.dart:3:7: Error: 'Priority' is an enum and can't be extended or implemented.
class Rush extends Priority {}
^
extend_enum.dart:3:7: Error: Non-abstract class 'Rush' has 'Enum' as a superinterface.
class Rush extends Priority {}
^
extend_enum.dart:3:7: Error: The superclass, 'Priority', has no unnamed constructor that takes no arguments.
class Rush extends Priority {}
^
Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run extend_enum.dart
The first message says it plainly: the enum can’t be extended or implemented. The two messages after it are the consequences of the same line. A fixed set of values is only fixed if no one can add a fifth by subclassing.
The enum header in Dart 3.13
Dart 3.13 can list an enum’s fields in its header, so that the const constructor does not have to be written out. The primary constructors lesson shows the same kind of enum in that form; everything in this lesson holds for both.
Checkpoint
Check: Fields, methods and rules of an enhanced enum
A switch over an enum has to name every value
route.dart sends each priority to a place that handles it, with a switch expression: A switch written as an expression, with comma-separated cases and => instead of a colon. It produces a value you can assign or return, and its cases must be exhaustive. More in “Dart switch statements and expressions” that has one arm per value and no fallback:
enum Priority { low, normal, high, urgent }
String route(Priority p) => switch (p) {
Priority.low => 'weekly digest',
Priority.normal => 'shared queue',
Priority.high => 'senior desk',
Priority.urgent => 'on-call phone',
};
void main() {
for (final p in Priority.values) {
print('${p.name} -> ${route(p)}');
}
} Output
low -> weekly digest normal -> shared queue high -> senior desk urgent -> on-call phone
Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run route.dart
Delete one arm and the program stops compiling. route_missing.dart removes the urgent arm from the expression and also
from an ordinary switch statement:
enum Priority { low, normal, high, urgent }
String route(Priority p) => switch (p) {
Priority.low => 'weekly digest',
Priority.normal => 'shared queue',
Priority.high => 'senior desk',
};
void log(Priority p) {
switch (p) {
case Priority.low:
print('digest');
case Priority.normal:
print('queue');
case Priority.high:
print('desk');
}
}
void main() {
print(route(Priority.urgent));
log(Priority.urgent);
} Output (exit status 254)
Printed as an error (standard error)
route_missing.dart:3:37: Error: The type 'Priority' is not exhaustively matched by the switch cases since it doesn't match 'Priority.urgent'.
- 'Priority' is from 'route_missing.dart'.
Try adding a wildcard pattern or cases that match 'Priority.urgent'.
String route(Priority p) => switch (p) {
^
route_missing.dart:10:11: Error: The type 'Priority' is not exhaustively matched by the switch cases since it doesn't match 'Priority.urgent'.
- 'Priority' is from 'route_missing.dart'.
Try adding a default case or cases that match 'Priority.urgent'.
switch (p) {
^
Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run route_missing.dart
Both switches were refused, and each message names the value left out, Priority.urgent. The branches page calls this
exhaustiveness: The compiler's check that a switch expression's cases cover every possible value of the matched type; for a bool, enum or sealed type a missing case is a compile-time error. More in “Dart switch statements and expressions” checking, a feature that reports a compile-time error if a value can
enter a switch and match no case, and it points out that enums are useful for exactly this reason: their values are
known and can all be listed. The compiler is doing the work that a code review would otherwise have to do by eye.
A wildcard case switches that check off. The session below analyzes two switches over a three-value enum, adds a fourth value to both, and analyzes again:
from session import files, run
# The same switch twice, once with every value named and once with a wildcard last case; then a fourth value is added.
files("wild/exhaustive.dart=exhaustive.dart", "wild/wildcard.dart=wildcard.dart")
run(
"""
dart analyze exhaustive.dart wildcard.dart
# now add a fourth value, urgent, to the enum in both files
sed 's/normal, high }/normal, high, urgent }/' exhaustive.dart > e.tmp && mv e.tmp exhaustive.dart
sed 's/normal, high }/normal, high, urgent }/' wildcard.dart > w.tmp && mv w.tmp wildcard.dart
head -n 1 exhaustive.dart wildcard.dart
!dart analyze exhaustive.dart wildcard.dart
"""
) Output
$ dart analyze exhaustive.dart wildcard.dart
Analyzing exhaustive.dart, wildcard.dart...
No issues found!
# now add a fourth value, urgent, to the enum in both files
$ sed 's/normal, high }/normal, high, urgent }/' exhaustive.dart > e.tmp && mv e.tmp exhaustive.dart
$ sed 's/normal, high }/normal, high, urgent }/' wildcard.dart > w.tmp && mv w.tmp wildcard.dart
$ head -n 1 exhaustive.dart wildcard.dart
==> exhaustive.dart <==
enum Level { low, normal, high, urgent }
==> wildcard.dart <==
enum Level { low, normal, high, urgent }
$ dart analyze exhaustive.dart wildcard.dart
Analyzing exhaustive.dart, wildcard.dart...
error - exhaustive.dart:3:26 - The type 'Level' isn't exhaustively matched by the switch cases since it doesn't match the pattern 'Level.urgent'. Try adding a wildcard pattern or cases that match 'Level.urgent'. - non_exhaustive_switch_expression
1 issue found.
[exit status 3]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 wildcard_session.py
The drawing shows the whole scene; its 4 steps are listed below.
Every step as text
- Three values, two switches. Level has low, normal and high. One switch names all three, the other names two and ends with a wildcard. dart analyze found no issue in either file.
- A fourth value arrives. The session adds urgent to the enum in both files with one sed command each. Nothing else changes, and no one has yet looked at the switches that read Level.
- The named switch complains. dart analyze reported an error in exhaustive.dart, saying that Level is not exhaustively matched since it does not match Level.urgent. The compiler found the place that needs a decision.
- The wildcard switch says nothing. wildcard.dart still analyzes without a message, because the wildcard matches urgent as well. From now on urgent takes the same branch as high, and nobody was asked whether that is right.
The switch that names every value became an error that points at the place to decide, and the one ending in _ sent
urgent down the same arm as high without a word. That is sometimes what you want. When it is not, leave the wildcard
out, so that the next person who adds a value is told about every switch that needs a new line.
Let the compiler list your work
After adding a value to an enum, run dart analyze and read the list of exhaustiveness errors. It is the to-do list of
every place that has to decide what the new value means.
Checkpoint
Check: Switches that must name every value
A dot shorthand leaves out the type the context already knows
Inside a switch over a Priority, every arm repeats the word Priority. Dart 3.10 added a shorter form, a
dot shorthand: An expression that starts with a dot, such as .paid, .parse('8080') or .new(1234), and leaves out the type name that the context type supplies. It needs a language version of 3.10 or higher.: an expression that starts with a dot and leaves out the type name when the
compiler can work it out. dots.dart uses it for an enum, a static method and three constructors of a small Fare class:
enum Status { pending, paid, refunded }
class Fare {
const Fare(this.paise);
const Fare.rupees(int rupees) : paise = rupees * 100;
factory Fare.parse(String text) => Fare((double.parse(text) * 100).round());
final int paise;
@override
String toString() => '₹${paise ~/ 100}.${(paise % 100).toString().padLeft(2, '0')}';
}
String describe(Status s) => switch (s) {
.pending => 'waiting for the bank',
.paid => 'money received',
.refunded => 'money returned',
};
void main() {
Status s = .paid;
print(s);
int port = .parse('8080');
print(port + 1);
Fare a = .rupees(250);
Fare b = .parse('99.5');
Fare c = .new(1234);
print('$a $b $c');
print(describe(.refunded));
print(s == .paid);
print(s != .pending);
Status? maybe = .pending;
print(maybe);
final steps = <Status>[.pending, .paid];
print(steps);
const Status fallback = .pending;
const Fare fixed = .rupees(10);
print('$fallback $fixed');
Status chosen = steps.length > 1 ? .paid : .pending;
print(chosen);
} Output
Status.paid 8081 ₹250.00 ₹99.50 ₹12.34 money returned true true Status.pending [Status.pending, Status.paid] Status.pending ₹10.00 Status.paid
Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run dots.dart
Each line of main that printed something used a different form:
| Written | Same as | Printed |
|---|---|---|
Status s = .paid; |
Status.paid |
Status.paid |
int port = .parse('8080'); |
int.parse('8080') |
8081 after port + 1 |
Fare a = .rupees(250); |
Fare.rupees(250) |
250 rupees as 25000 paise |
Fare b = .parse('99.5'); |
the factory Fare.parse('99.5') |
99.50 rupees |
Fare c = .new(1234); |
the unnamed constructor Fare(1234) |
12.34 rupees |
.refunded as an argument |
Status.refunded |
money returned |
.pending => '…' in a switch |
Status.pending => '…' |
each arm |
The same short forms worked in a const declaration, in a list literal whose element type is Status, in a conditional
expression and on the right of ==, and a nullable Status? accepted .pending too. The dot shorthands page lists
enums, named constructors, unnamed constructors through .new, static members and constant expressions as the places it
works. Follow one line through the compiler:
The drawing shows the whole scene; its 4 steps are listed below.
Every step as text
- A dot with no name before it. The right side starts with a dot, so the type name is missing. The compiler cannot guess it from the dot, so it looks to the left, where the variable says what it holds.
- Left side gives the context. The variable a is declared as Fare, so the expression on the right has to be a Fare. That expected type is the context type, and the shorthand is read against it.
- The name is looked up in Fare. With Fare as the context, .rupees means Fare.rupees, a constructor that is declared in the class. A name that Fare does not declare is an error at this step, and the message names it.
- The same object as the long form. The result is exactly what Fare.rupees(250) builds, 25000 paise, which the recorded output showed as 250 rupees. The shorthand changed how the line is written and nothing about the program.
The shorthand changes how a line is written and nothing about the program. It is also a language feature with a version: a package whose minimum SDK is older than 3.10 cannot use it. version_session.py puts one file under two pubspecs:
from session import files, run
# One package, one file with a dot shorthand, two minimum SDK versions in the pubspec.
files("old/bin=bin", "old/pubspec_39.yaml=pubspec_39.yaml", "old/pubspec_310.yaml=pubspec_310.yaml")
run(
"""
cp pubspec_39.yaml pubspec.yaml
cat pubspec.yaml
dart pub get --offline > /dev/null
!dart analyze
!dart run bin/main.dart
cp pubspec_310.yaml pubspec.yaml
dart pub get --offline > /dev/null
dart analyze
dart run bin/main.dart
"""
) Output
$ cp pubspec_39.yaml pubspec.yaml
$ cat pubspec.yaml
name: shorthand_demo
environment:
sdk: ^3.9.0
$ dart pub get --offline > /dev/null
$ dart analyze
Analyzing demo...
error - bin/main.dart:4:14 - This requires the 'dot-shorthands' language feature to be enabled. Try updating your pubspec.yaml to set the minimum SDK constraint to 3.10.0 or higher, and running 'pub get'. - experiment_not_enabled
1 issue found.
[exit status 3]
$ dart run bin/main.dart
bin/main.dart:4:14: Error: This requires the experimental 'dot-shorthands' language feature to be enabled.
Try passing the '--enable-experiment=dot-shorthands' command line option.
Status s = .paid;
^
[exit status 254]
$ cp pubspec_310.yaml pubspec.yaml
$ dart pub get --offline > /dev/null
$ dart analyze
Analyzing demo...
No issues found!
$ dart run bin/main.dart
Status.paid
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 version_session.py
With sdk: ^3.9.0 the analyzer reported that the feature needs to be enabled and said what to change: set the minimum SDK
constraint to 3.10.0 or higher and run pub get. With ^3.10.0 the file analyzed clean and ran. The timeline shows how
the features of this lesson arrived, which tells you what a package must require:
- 2.14
The Enum class enums
The Enum class, which every enum extends, and its index property are marked as available since 2.14.
- 2.15
name, byName, asNameMap lookup
The name property, the lookup byName and the map builder asNameMap are each marked as available since 2.15.
- 2.17
Enhanced enums enums
Fields, methods, const constructors and implemented interfaces on enums need a language version of at least 2.17, as the enums page says.
- 3.0
Switch expressions switch
Switch expressions need a language version of at least 3.0, together with patterns, and a switch over an enum is checked for exhaustiveness.
- 3.10
Dot shorthands syntax
A leading dot that leaves out the type, as in Status s = .paid, needs a language version of at least 3.10, the dot shorthands page says.
- 3.13
Primary constructors on enums syntax
From Dart 3.13 an enum can list its fields in the header, as in enum Vehicle(final int tires), instead of a separate const constructor.
The timeline has 6 events, from 2.14 to 3.13.
Checkpoint
Check: Reading a dot shorthand
Where a dot shorthand has no context type, it fails
A shorthand has no type of its own, so it can only be read where the code expects a particular type. dot_limits.dart puts it in three places where nothing does:
enum Status { pending, paid }
class Log {
static void line(String text) => print(text);
}
void main() {
var a = .paid;
Status s = .pending;
if (.paid == s) {
print('paid');
}
.line('hello');
} Output (exit status 254)
Printed as an error (standard error)
dot_limits.dart:8:12: Error: No type was provided to find the dot shorthand 'paid'.
var a = .paid;
^^^^
dot_limits.dart:10:8: Error: No type was provided to find the dot shorthand 'paid'.
if (.paid == s) {
^^^^
dot_limits.dart:13:4: Error: No type was provided to find the dot shorthand 'line'.
.line('hello');
^^^^
Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run dot_limits.dart
The compiler reported all three, each as ‘No type was provided to find the dot shorthand’, with the name it could not
place. In var a = .paid; the variable has no declared type to supply one. In .paid == s the shorthand stands on the
left, and the dot shorthands page says the == and != operators read the type of the left operand for a shorthand on
the right, so s == .paid compiles and .paid == s does not. And an expression statement may not begin with a dot, so
.line('hello') is refused although Log has a static method of that name.
Writing the shorthand where nothing says the type
var level = .high; and .high == level both fail. Give the variable a type, Priority level = .high;, or put the
shorthand on the right of the comparison, level == .high.
Use a shorthand where the type is obvious from the same line, such as the declared type of a variable or the arms of a switch over a parameter, and write the full name where a reader would have to look up what the dot means.
Checkpoint
Check: The context type a shorthand needs
Practise: a traffic light that cannot be misread
The package is small: its pubspec depends on package:test for development only, its library declares an enum whose members
all throw, and its test file holds four sample tests.
practice/pubspec.yaml
name: practice
description: A traffic light enum with a lookup and an advice function, and four sample tests.
publish_to: none
environment:
sdk: ^3.13.0
dev_dependencies:
test: ^1.33.0 lib/practice.dart · practice/lib/practice.dart
/// The three lights of a crossing, in the order they follow each other.
enum TrafficLight {
red,
green,
amber;
/// How long the light stays on: red 30, green 25, amber 5 seconds.
int get seconds => throw UnimplementedError('seconds');
/// The light that follows: red, then green, then amber, then red again.
TrafficLight next() => throw UnimplementedError('next');
/// The light called [text] (spaces around it and capital letters are
/// ignored), or null when no light has that name.
static TrafficLight? fromName(String text) =>
throw UnimplementedError('fromName');
}
/// What a driver should do at [light]: 'stop' at red, 'go' at green and
/// 'slow down' at amber. Use an exhaustive switch with dot shorthands.
String advise(TrafficLight light) => throw UnimplementedError('advise'); test/practice_test.dart · practice/test/practice_test.dart
import 'package:practice/practice.dart';
import 'package:test/test.dart';
void main() {
test('each light stays on for its time', () {
expect([for (final l in TrafficLight.values) l.seconds], [30, 25, 5]);
});
test('the lights follow each other in a circle', () {
expect(TrafficLight.red.next(), TrafficLight.green);
expect(TrafficLight.amber.next(), TrafficLight.red);
});
test('a name is found in any case, and an unknown name is null', () {
expect(TrafficLight.fromName(' Green '), TrafficLight.green);
expect(TrafficLight.fromName('blue'), isNull);
});
test('advice for each light', () {
expect(advise(TrafficLight.amber), 'slow down');
expect(advise(TrafficLight.red), 'stop');
});
} Running the starter as given prints the library and then four failures:
from session import files, run
# The practice package as handed out: every member still throws, so all four sample tests fail.
files("practice=traffic_practice")
run(
"""
cd traffic_practice
cat lib/practice.dart
dart pub get --offline > /dev/null
!dart test --reporter failures-only
""",
pub=True,
) Output
$ cd traffic_practice
$ cat lib/practice.dart
/// The three lights of a crossing, in the order they follow each other.
enum TrafficLight {
red,
green,
amber;
/// How long the light stays on: red 30, green 25, amber 5 seconds.
int get seconds => throw UnimplementedError('seconds');
/// The light that follows: red, then green, then amber, then red again.
TrafficLight next() => throw UnimplementedError('next');
/// The light called [text] (spaces around it and capital letters are
/// ignored), or null when no light has that name.
static TrafficLight? fromName(String text) =>
throw UnimplementedError('fromName');
}
/// What a driver should do at [light]: 'stop' at red, 'go' at green and
/// 'slow down' at amber. Use an exhaustive switch with dot shorthands.
String advise(TrafficLight light) => throw UnimplementedError('advise');
$ dart pub get --offline > /dev/null
$ dart test --reporter failures-only
+0 -1: test/practice_test.dart: each light stays on for its time [E]
UnimplementedError: seconds
package:practice/practice.dart 8:22 TrafficLight.seconds
test/practice_test.dart 6:52 main.<fn>
+0 -2: test/practice_test.dart: the lights follow each other in a circle [E]
UnimplementedError: next
package:practice/practice.dart 11:26 TrafficLight.next
test/practice_test.dart 10:29 main.<fn>
+0 -3: test/practice_test.dart: a name is found in any case, and an unknown name is null [E]
UnimplementedError: fromName
package:practice/practice.dart 16:7 TrafficLight.fromName
test/practice_test.dart 15:25 main.<fn>
+0 -4: test/practice_test.dart: advice for each light [E]
UnimplementedError: advise
package:practice/practice.dart 21:38 advise
test/practice_test.dart 20:12 main.<fn>
+0 -4: Some tests failed.
Consider enabling the flag chain-stack-traces to receive more detailed exceptions.
For example, 'dart test --chain-stack-traces'.
[exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 practice_session.py
- Save the three files in a folder of your own, run
dart pub get, and rundart test; four failures should appear, as in the session. - Give each light its time in seconds through a field and a const constructor, so that
secondsstops throwing. - Write
next()as a switch expression with dot shorthands in the arms, andfromNameon the map fromasNameMap(), trimming the text and ignoring capital letters. - Write
adviseas a switch with no wildcard. Then add a fourth light to the enum and read whatdart analyzesays.
After dart test ends with ‘All tests passed!’, answer the questions to see what has stuck from each section:
Practice that adapts to you
Practise this lesson
11 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up (fresher): What does values.byName do when no enum value has the given name, and what would you use for text typed by a user?
byName throws an ArgumentError, with the message “No enum value with that name” and the unknown text in quotes, so a misspelt name stops whatever called it. That is the right behaviour when your own code wrote the name and a missing value is a bug. Text typed by a user, read from a file or received from a network call can be wrong in ordinary ways, so build a map once with values.asNameMap() and read it with the text as the key. A name that is not there reads as null, and the caller decides what that means: a default priority, or a message that lists the valid names. Trim the text and fold its case before the lookup if the input is typed by hand.
Next come two scenario questions from the bank, one about stored data and one about versions:
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Mid to senior
A helpdesk app stores each ticket's Priority.index in a database column. Someone sorts the enum's values alphabetically to tidy the file. After the release, old tickets show the wrong priority and nothing crashed. Explain what happened, how you repair the data, and how you store the value from now on.
-
Mid to senior
A published package declares sdk: ^3.6.0. A contributor with a brand-new SDK installed wants dot shorthands such as .paid across the code. What does the analyzer say, what must change in the package, and who is affected by the change?
Flashcards · 6 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
What does Priority.values.byName('urgnt') do when no value has that name?
It throws an ArgumentError, "No enum value with that name". For text typed by a user, read Priority.values.asNameMap()[text] instead and handle the null.
-
What is an enhanced enum?
A class with a fixed number of const instances: its entries call a const constructor, its fields are final, it may implement interfaces, and no other class can extend it.
-
Where should a rule that differs per enum value, such as a reply time, live?
In a final field of the enum, so the rule sits next to the value it belongs to and cannot drift away from it.
-
What happens to a switch over an enum when you add a value?
Without a wildcard, every switch that ignores the new value becomes a compile-time error, so dart analyze lists every place to decide; a wildcard case would hide it.
-
What is a dot shorthand, and which SDK does a package need for it?
Writing .paid, .parse('8080') or .new(1234) without the type name that the context type supplies. It needs a package whose minimum SDK is 3.10 or higher.
-
Where does a dot shorthand fail?
Where no type is expected: on a var, on the left of ==, and at the start of a statement. Give the variable a type, Priority level = .high;, or move the shorthand right: level == .high.
Key takeaways
- An enum is a closed set of constant values with a name and an index, and
values.byNamethrows an ArgumentError on an unknown name, so text from outside is looked up inasNameMap()and a null is handled. - An enhanced enum is a class with a fixed number of const instances: its entries call a const constructor, its fields are final, it may implement interfaces, and no other class can extend it.
- A switch over an enum without a wildcard is checked for every value, so a new value turns each switch that ignores it into a compile-time error, while a wildcard case hides the new value.
- A dot shorthand such as
.paid,.parse('8080')or.new(1234)leaves out the type name that the context type supplies, and it needs a package whose minimum SDK is 3.10 or higher. - A shorthand fails where no type is expected: on a
var, on the left of==, and at the start of a statement, so the type goes on the variable or the shorthand moves to the right.
Terms in this lesson
- context type
- The type that the surrounding code expects an expression to have, such as the declared type of the variable it is assigned to. A dot shorthand is read against it, and fails where there is none.
- dot shorthand
- An expression that starts with a dot, such as .paid, .parse('8080') or .new(1234), and leaves out the type name that the context type supplies. It needs a language version of 3.10 or higher.
- enhanced enum
- An enum that has final fields, a const constructor, methods, getters or implemented interfaces, available since Dart 2.17. It still has a fixed set of instances and cannot be extended.
- enum
- A special kind of class with a fixed number of constant values, declared with enum. Each value is one shared object with a name and an index, and Priority.values lists them in the order written.
References
- Enumerated types (The Dart project (Google))
- Dot shorthands (The Dart project (Google))
- Branches (exhaustiveness checking) (The Dart project (Google))
- EnumByName extension (The Dart project (Google))
- Enum class (The Dart project (Google))
- Pubspec file (SDK constraints) (The Dart project (Google))
Report a problem with this lesson
Kept only in this browser. Your Learn progress