Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

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.

  • Beginner
  • 15 minutes
  • Examples run with dart 3.13.5 and Python 3.14.8
  • By MySmartCoPilot
Free lesson, Pro answers: 15 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

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:

lookup.dart: names, indexes and two ways to look a value up Dart · lookup.dart
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:

Interactive · step by step One unknown name, two lookups Step through lookup.dart. The same text goes to byName and to a map built by asNameMap, and each answers differently.
One unknown name, two lookups, step 4 of 4. Showing text "unknown", values.byName, asNameMap()[text], ArgumentError (the program stops here), null. In focus: values.byName, asNameMap()[text].throwsreturnstext "unknown"values.byNameasNameMap()[text]ArgumentErrorthe program stops herenullnull is a value to handle

The drawing shows the whole scene; its 4 steps are listed below.

Every step as text
  1. 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.
  2. 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.
  3. 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.
  4. 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

  1. Question 1 of 2 A form sends the text 'urgnt'. Which expression gives null instead of stopping the request?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Priority.values.asNameMap()['urgnt']

    asNameMap builds a map from each name to its value, so a name that is not there reads as null, which the caller can test. byName is meant for names that your own code wrote.

  2. Question 2 of 2 With enum Priority { low, normal, high, urgent }, what does Priority.high.index give?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: 2

    index is the zero-based place of the value in the declaration, which is why inserting a value in the middle changes the index of every value after it.

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:

priority.dart: an enum with a field, getters and Comparable Dart · priority.dart
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:

Interactive · step by step Four constants, built once Step through the Priority enum of priority.dart. Each constant is one object, and the values list keeps them in the order written.
Four constants, built once, step 4 of 4. Showing enum Priority, Priority.values (low, normal, high, urgent), low: 72 h, normal: 24 h, high: 4 h, urgent: 1 h. In focus: normal: 24 h, high: 4 h, urgent: 1 h.listsnextnextenum PriorityPriority.valueslow, normal, high, urgentlow: 72 hnormal: 24 hhigh: 4 hurgent: 1 hnext stays here

The drawing shows the whole scene; its 4 steps are listed below.

Every step as text
  1. 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.
  2. 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.
  3. 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.
  4. 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:

extend_enum.dart: a class that extends an enum Dart · extend_enum.dart
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

  1. Question 1 of 2 Which members may an enhanced enum declare?

    Choose every answer that is right.

    Show the answer: Question 1 of 2

    Answer:

    • A final field that a const constructor sets
    • A getter that compares this with one of the constants

    An enhanced enum is a class with a fixed number of const instances, so its fields are final, its constructors are const, and it may have getters, methods and interfaces, but nothing that clashes with the generated members.

  2. Question 2 of 2 extend_enum.dart printed three errors for the single line class Rush extends Priority {}. What do they share as a cause?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: An enum can't be extended or implemented

    A fixed set of values stays fixed only if no subclass can add another, so the compiler refuses the line and then reports what follows from it.

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:

route.dart: one arm for every priority Dart · route.dart
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:

route_missing.dart: a value that no case names Dart · route_missing.dart
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:

Adding urgent to an enum that two switches read Python · wildcard_session.py
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

Interactive · step by step Adding a value to an enum, seen by two switches Step through wildcard_session.py. Two switches read the same Level enum, one naming every value and one ending in a wildcard.
Adding a value to an enum, seen by two switches, step 4 of 4. Showing enum Level (low, normal, high, urgent), switch, all named, switch ending in _, compile error, no message. In focus: switch ending in _, no message.enum Levellow, normal, high, urgentswitch, all namedswitch ending in _compile errorno message

The drawing shows the whole scene; its 4 steps are listed below.

Every step as text
  1. 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.
  2. 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.
  3. 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.
  4. 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

  1. Question 1 of 2 In route_missing.dart the urgent arm is removed from a switch expression. What did the compiler do?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Refused to compile and named Priority.urgent as the value no case matches

    Exhaustiveness checking reports a compile-time error when a value can enter a switch and match no case.

  2. Question 2 of 2 A switch over Level ends with _ => 'desk'. A fourth value, urgent, is added to Level. What does dart analyze say?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Nothing, and urgent now takes the wildcard arm

    A wildcard turns the check off for the values it covers, so a new value goes down the wildcard arm without anyone being asked.

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:

dots.dart: dot shorthands on enums, statics and constructors Dart · dots.dart
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:

Interactive · step by step How the compiler reads Fare a = .rupees(250) Step through one line of dots.dart. The type written on the left decides what the dot on the right means.
How the compiler reads Fare a = .rupees(250), step 4 of 4. Showing Fare a = .rupees(250), context type Fare, Fare.rupees, Fare of 25000 paise. In focus: Fare of 25000 paise.expectslooks inbuildsFare a = .rupees(250)context type FareFare.rupeesFare of 25000 paise

The drawing shows the whole scene; its 4 steps are listed below.

Every step as text
  1. 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.
  2. 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.
  3. 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.
  4. 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:

The same dot shorthand under two minimum SDK versions Python · version_session.py
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:

Timeline · 6 events in order Where each feature came from Walk through the releases in order. A pubspec whose minimum SDK is older than a feature's release cannot use it.
  1. 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. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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

  1. Question 1 of 2 What does this print?

    Read the code, then choose one answer.

    enum Status { pending, paid }
    
    void main() {
      Status s = .paid;
      int n = .parse('7');
      print('$s ${n + 1}');
    }
    Show the answer: Question 1 of 2

    Answer: Status.paid 8

    Both lines are read against the type the variable declares: Status for .paid and int for .parse('7').

  2. Question 2 of 2 A package says sdk: ^3.9.0 and has the line Status s = .paid;. What does dart analyze report?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: An error that the dot-shorthands feature is not enabled, and a hint to raise the minimum SDK to 3.10.0

    A feature belongs to the language version in the pubspec's constraint. Raise the minimum SDK to 3.10.0 or higher, run pub get, and the shorthand is accepted.

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:

dot_limits.dart: three shorthands without a context type Dart · dot_limits.dart
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

  1. Question 1 of 2 Given enum Level { low, high }, which line compiles?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Level a = .high;

    A shorthand needs a type that the surrounding code expects, such as the declared type of a variable.

  2. Question 2 of 2 With Level level = Level.low; and a bool cond, which comparison compiles?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: level == .high

    == and != take the context for a shorthand from the left operand's static type, and only for a shorthand written directly on the right.

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.

The traffic light package: stubs and 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:

The starter package and its four failing sample tests Python · practice_session.py
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

  1. Save the three files in a folder of your own, run dart pub get, and run dart test; four failures should appear, as in the session.
  2. Give each light its time in seconds through a field and a const constructor, so that seconds stops throwing.
  3. Write next() as a switch expression with dot shorthands in the arms, and fromName on the map from asNameMap(), trimming the text and ignoring capital letters.
  4. Write advise as a switch with no wildcard. Then add a fourth light to the enum and read what dart analyze says.

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.

  1. Question 1 of 11 Which lookup is meant for a name that your own code wrote and that must exist?

    Choose one answer.

    Show the answer: Question 1 of 11

    Answer: values.byName

    byName stops on an unknown name, which suits names written in your own code; text from outside goes through the map.

  2. Question 2 of 11 A table stores Priority.index. A developer inserts a new value before urgent. What happens to the stored rows?

    Choose one answer.

    Show the answer: Question 2 of 11

    Answer: Rows that held urgent now read as the new value, because the indexes after the insert moved

    Store a name or an explicit code that you declare, and look it up with a map, so that edits to the declaration cannot change the meaning of old rows.

  3. Question 3 of 11 Where does the reply time of each priority belong?

    Choose one answer.

    Show the answer: Question 3 of 11

    Answer: In a final field of the enum, set by a const constructor

    Data that differs from value to value is a field of the enhanced enum, as hoursToReply was.

  4. Question 4 of 11 Why can an enum not override == and hashCode?

    Choose one answer.

    Show the answer: Question 4 of 11

    Answer: Each constant is one shared object, so two values are equal only when they are the same constant

    The constants are built once and shared, so identity is the right and only equality.

  5. Question 5 of 11 Why is a switch over an enum without a wildcard safer when a value is added?

    Choose one answer.

    Show the answer: Question 5 of 11

    Answer: The compiler reports each switch that does not name the new value

    The error list after adding a value is the list of places that must decide what the value means.

  6. Question 6 of 11 A switch statement over Priority names low, normal and high only, with no default. What does Dart 3.13 do?

    Choose one answer.

    Show the answer: Question 6 of 11

    Answer: Reports an error that Priority.urgent is not matched

    Both forms of switch are checked for every value of an enum, unless a default or wildcard case covers the rest.

  7. Question 7 of 11 A team wants every switch over Priority to keep working when a fifth value is added, without edits. What do they give up with a wildcard?

    Choose one answer.

    Show the answer: Question 7 of 11

    Answer: The compiler's list of places where the new value needs a decision

    A wildcard trades the compiler's reminder for convenience, which is a choice to make switch by switch.

  8. Question 8 of 11 Which line is the same as int port = int.parse('8080');?

    Choose one answer.

    Show the answer: Question 8 of 11

    Answer: int port = .parse('8080');

    A shorthand is the member name with a leading dot, read against the type the surrounding code expects.

  9. Question 9 of 11 Fare c = .new(1234); builds which object?

    Choose one answer.

    Show the answer: Question 9 of 11

    Answer: The same as Fare(1234), through the unnamed constructor

    .new calls the unnamed constructor of the context type with the arguments given.

  10. Question 10 of 11 Why does print(.paid == s) fail while print(s == .paid) compiles?

    Choose one answer.

    Show the answer: Question 10 of 11

    Answer: A shorthand on the right of == takes its type from the left operand, and one on the left has no type to read

    The left operand supplies the context type for a shorthand written directly on the right of == or !=.

  11. Question 11 of 11 Which line compiles, if any?

    Choose one answer.

    Show the answer: Question 11 of 11

    Answer: int n = .parse('7');

    The context type reaches a shorthand that is the whole right side of a declaration, an argument or an arm, not one that starts a larger expression.

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.

  1. 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.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. 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?

    Model answer, Pro. First step and follow-ups

Flashcards · 6 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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.byName throws an ArgumentError on an unknown name, so text from outside is looked up in asNameMap() 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

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.