Watch the video (10 min)
On this page22 sections
  1. Prerequisites
  2. Connection With Previous Lectures
  3. Learning Objectives
  4. 1. Only One Unnamed Constructor
  5. 2. Named Constructors
  6. 3. Converting Values in a Named Constructor
  7. 4. Redirecting Constructors
  8. 5. const Constructors
  9. 6. Canonical Instances and identical()
  10. 7. When const Is Impossible
  11. 8. Factory Constructors
  12. 9. A Factory That Reuses Objects
  13. 10. A Real-World Example
  14. Code Examples
  15. Common Beginner Mistakes
  16. Best Practices
  17. Mini Challenge
  18. Practice Questions
  19. Practice Answers
  20. Quick Revision
  21. Key Takeaways
  22. What's Next?

Lecture 55 — Named & Constant Constructors

Category9 — Object-Oriented Programming
Lecture055 of 140
Estimated video length10 min
TargetDart 3.12.2
LevelBeginner

A class with one constructor has only one way to be built. Named constructors give it several clearly labelled ways, and const constructors let equal objects be shared.

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 001–054:

  • classes, objects, fields and methods (Lecture 053)
  • constructors with this.field, required named parameters and initializer lists (Lecture 054)
  • final and const (Lecture 006) and const lists (Lecture 046)
  • identical() (Lecture 018) and maps with ?? (Lectures 014 and 049)

Connection With Previous Lectures

In Lecture 054, every class had one constructor:

dart
class Temperature {
  final double celsius;
  Temperature(this.celsius);
}

Now Sara reads a thermometer that shows Fahrenheit. To build a Temperature, she must convert the number herself before every call — and so must everyone else who uses the class. The class knows how to store Celsius, but it cannot be built from anything else. We need a second, clearly named way in.


Learning Objectives

By the end of this lecture you will be able to:

  • Explain why a class cannot have two unnamed constructors
  • Create named constructors such as Point.origin()
  • Use initializer lists inside named constructors
  • Write redirecting constructors with : this(...)
  • Create const constructors and constant objects
  • Predict when two objects are identical()
  • Recognize when a const constructor is impossible
  • Write simple factory constructors, including one that reuses objects

1. Only One Unnamed Constructor

The constructor named after the class is the unnamed constructor. A class can have only one. Trying to add a second one for Fahrenheit fails:

dart
class Temperature {
  final double celsius;
  Temperature(this.celsius);
  // Temperature(double fahrenheit) : celsius = (fahrenheit - 32) * 5 / 9; // ✗
}
output
Error: 'Temperature' is already declared in this scope.

Two constructors with the same name clash, even with different parameters.


2. Named Constructors

A named constructor adds a name after a dot: ClassName.name(...).

Analogy — a shop has a front door, a side door and a delivery door. Every door leads into the same shop; the name tells you which way you are coming in.

dart
class Point {
  final int x;
  final int y;
  Point(this.x, this.y);
  Point.origin() : x = 0, y = 0;
}

void main() {
  var a = Point(3, 4);
  var b = Point.origin();
  print('${a.x}, ${a.y}');
  print('${b.x}, ${b.y}');
}
output
3, 4
0, 0

Point.origin() needs no arguments: its initializer list sets both fields. The call reads like a sentence — "a point at the origin".


3. Converting Values in a Named Constructor

Named constructors solve Sara's problem. The conversion moves into the class, once:

dart
class Temperature {
  final double celsius;
  Temperature(this.celsius);
  Temperature.fromFahrenheit(double fahrenheit)
    : celsius = (fahrenheit - 32) * 5 / 9;
}

void main() {
  var today = Temperature(25);
  var reading = Temperature.fromFahrenheit(98.6);
  print(today.celsius);
  print(reading.celsius.toStringAsFixed(1));
}
output
25.0
37.0

A class may have as many named constructors as it needs, each with its own parameters — positional, named or required, exactly as in Lecture 054.


4. Redirecting Constructors

Writing x = 0, y = 0 repeats work the main constructor already does. A redirecting constructor hands the job to another constructor with : this(...):

dart
class Point {
  final int x;
  final int y;
  Point(this.x, this.y);
  Point.origin() : this(0, 0);
  Point.onXAxis(int x) : this(x, 0);
}

void main() {
  var p = Point.onXAxis(7);
  print('${p.x}, ${p.y}');
  print(Point.origin().y);
}
output
7, 0
0

A redirecting constructor does nothing else: no body, no other initializers. If the main constructor later gains a check, every redirect gets it for free.


5. const Constructors

In Lecture 006 we said const would come back for objects. Put const before a constructor, make every field final, and the class can create constant objects — fixed before the program runs:

dart
class Point {
  final int x;
  final int y;
  const Point(this.x, this.y);
}

void main() {
  const corner = Point(10, 20);
  print('${corner.x}, ${corner.y}');
}
output
10, 20

Like a const list, a const object can never change — its fields are final, and the whole object is decided while you write the code.


6. Canonical Instances and identical()

Dart creates one object for each distinct constant. Writing the same constant twice gives you the same object back — the canonical instance:

dart
class Point {
  final int x;
  final int y;
  const Point(this.x, this.y);
}

void main() {
  const a = Point(1, 2);
  const b = Point(1, 2);
  var c = Point(1, 2);
  var d = Point(1, 2);
  print(identical(a, b));
  print(identical(c, d));
  print(identical(a, c));
}
output
true
false
false

a and b are one shared object. c and d were called without const, so each call built a new object, even though the constructor is const. A const constructor allows constant objects; you still ask for one with const.

== on your own objects compares identity for now; teaching a class what "equal" means comes in Lecture 060 — Method Overriding.


7. When const Is Impossible

A const constructor has three rules, and Dart enforces each one.

Every field must be final:

dart
class Point {
  int x;
  int y;
  // const Point(this.x, this.y); // ✗
}
output
Error: Constructor is marked 'const' so all fields must be final.

The constructor cannot have a body:

dart
class Point {
  final int x;
  final int y;
  // const Point(this.x, this.y) { print('made'); } // ✗
}
output
Error: A const constructor can't have a body.
Try removing either the 'const' keyword or the body.

A const object needs constant arguments. A value worked out while the program runs cannot go in:

dart
void main() {
  var x = 3;
  // const p = Point(x, 0); // ✗ x is not a constant
  final p = Point(x, 0); // ✓ a normal object
  print(p.x);
}
output
Error: Not a constant expression.

That last case is not a problem with the class — Point(x, 0) without const works fine.


8. Factory Constructors

Every constructor so far builds a new object. A factory constructor is a normal function body that must return an object of the class — however it likes. Reading an object out of a map is a typical job:

dart
class Student {
  final String name;
  final int marks;
  Student(this.name, this.marks);

  factory Student.fromMap(Map<String, Object> map) {
    var name = map['name'] as String? ?? 'Unknown';
    var marks = map['marks'] as int? ?? 0;
    return Student(name, marks);
  }
}

void main() {
  var ali = Student.fromMap({'name': 'Ali', 'marks': 85});
  var blank = Student.fromMap({});
  print('${ali.name}: ${ali.marks}');
  print('${blank.name}: ${blank.marks}');
}
output
Ali: 85
Unknown: 0

A map value is an Object?, so as String? treats it as a nullable String (Lecture 011) and ?? supplies a default. The factory can run any such code before it calls a real constructor. It has no this, because no object exists yet when it starts. Turning text into such maps is the job of Lecture 108 — JSON and Lecture 109 — JSON & Dart Objects.


9. A Factory That Reuses Objects

A factory may also return an object that already exists. Here a map remembers every badge made so far:

dart
final badgeCache = <String, Badge>{};

class Badge {
  final String name;
  Badge.create(this.name) {
    print('Creating $name badge');
  }

  factory Badge(String name) {
    return badgeCache.putIfAbsent(name, () => Badge.create(name));
  }
}

void main() {
  var first = Badge('Gold');
  var second = Badge('Gold');
  print(identical(first, second));
}
output
Creating Gold badge
true

The unnamed constructor is the factory, so callers write a plain Badge('Gold'); the named Badge.create does the real building. A cache that belongs to the class itself uses static, coming in Lecture 058 — Static Members.


10. A Real-World Example

dart
class Account {
  final String owner;
  final double balance;
  const Account(this.owner, this.balance);
  const Account.empty(String owner) : this(owner, 0);

  factory Account.fromMap(Map<String, Object> map) {
    var owner = map['owner'] as String? ?? 'Guest';
    var balance = map['balance'] as double? ?? 0;
    return Account(owner, balance);
  }
}

void main() {
  const guest = Account.empty('Guest');
  var zayan = Account('Zayan', 150);
  var sara = Account.fromMap({'owner': 'Sara', 'balance': 2000.0});

  for (var account in [guest, zayan, sara]) {
    print('${account.owner}: ${account.balance}');
  }
  print(identical(guest, const Account('Guest', 0)));
}
output
Guest: 0.0
Zayan: 150.0
Sara: 2000.0
true

Three doors: the main constructor, a redirecting const named one (a redirect may be const when its target is), and a factory reading a map. Account.empty('Guest') and Account('Guest', 0) describe the same constant, so they are the same object.


Code Examples

Example 1 — A Named Constructor

dart
class Point {
  final int x;
  final int y;
  Point(this.x, this.y);
  Point.origin() : x = 0, y = 0;
}

void main() {
  var p = Point.origin();
  print('(${p.x}, ${p.y})');
}
output
(0, 0)

Example 2 — Two Named Constructors

dart
class Student {
  final String name;
  final int grade;
  Student(this.name, this.grade);
  Student.newcomer(this.name) : grade = 1;
  Student.guest() : name = 'Guest', grade = 0;
}

void main() {
  var zayan = Student.newcomer('Zayan');
  var visitor = Student.guest();
  print('${zayan.name}, grade ${zayan.grade}');
  print('${visitor.name}, grade ${visitor.grade}');
}
output
Zayan, grade 1
Guest, grade 0

Example 3 — Redirecting

dart
class Rectangle {
  final int width;
  final int height;
  Rectangle(this.width, this.height);
  Rectangle.square(int side) : this(side, side);
}

void main() {
  var tile = Rectangle.square(5);
  print(tile.width * tile.height);
}
output
25

Example 4 — A const Object

dart
class Color {
  final int red;
  final int green;
  final int blue;
  const Color(this.red, this.green, this.blue);
}

void main() {
  const white = Color(255, 255, 255);
  print('${white.red}, ${white.green}, ${white.blue}');
}
output
255, 255, 255

Example 5 — Same Constant, Same Object

dart
class Color {
  final int red;
  final int green;
  final int blue;
  const Color(this.red, this.green, this.blue);
}

void main() {
  const a = Color(0, 0, 0);
  const b = Color(0, 0, 0);
  print(identical(a, b));
  print(identical(Color(0, 0, 0), Color(0, 0, 0)));
}
output
true
false

The last line calls the constructor without const, so it builds two separate objects.

Example 6 — A const List of Objects

dart
class Point {
  final int x;
  final int y;
  const Point(this.x, this.y);
}

void main() {
  const corners = [Point(0, 0), Point(0, 5), Point(5, 5)];
  print(corners.length);
  print(identical(corners[0], const Point(0, 0)));
}
output
3
true

Inside a const list, every element is constant automatically — no extra const needed.

Example 7 — Student Marks

dart
class Report {
  final String student;
  final int total;
  Report(this.student, this.total);

  factory Report.fromMarks(String student, Map<String, int> marks) {
    var total = 0;
    for (var mark in marks.values) {
      total += mark;
    }
    return Report(student, total);
  }
}

void main() {
  var marks = {'Math': 80, 'Science': 75, 'English': 85};
  var report = Report.fromMarks('Ali', marks);
  print('${report.student}: ${report.total}');
}
output
Ali: 240

How it works

  1. The factory adds up the map's values before any Report exists.
  2. It then calls the main constructor with the finished total.

Example 8 — Shopping Item with a Discount

dart
class Item {
  final String name;
  final double price;
  Item(this.name, this.price);
  Item.discounted(String name, double price, double rate)
    : this(name, price - price * rate);
}

void main() {
  var bag = Item.discounted('Bag', 1500, 0.2);
  print('${bag.name}: ${bag.price}');
}
output
Bag: 1200.0

Example 9 — Bank Account

dart
class Account {
  final String owner;
  final double balance;
  const Account(this.owner, this.balance);
  const Account.empty(String owner) : this(owner, 0);
}

void main() {
  var sara = Account('Sara', 2000);
  const zayan = Account.empty('Zayan');
  print('${sara.owner}: ${sara.balance}');
  print('${zayan.owner}: ${zayan.balance}');
}
output
Sara: 2000.0
Zayan: 0.0

Example 10 — Temperature Both Ways

dart
class Temperature {
  final double celsius;
  Temperature(this.celsius);
  Temperature.fromFahrenheit(double f) : celsius = (f - 32) * 5 / 9;
}

void main() {
  var readings = [Temperature(30), Temperature.fromFahrenheit(212)];
  for (var t in readings) {
    print('${t.celsius} °C');
  }
}
output
30.0 °C
100.0 °C

How it works

  1. Both constructors produce the same kind of object, so they fit in one list.
  2. (212 - 32) * 5 / 9 is 100.0.

Common Beginner Mistakes

1. Two unnamed constructors

dart
class Temperature {
  final double celsius;
  Temperature(this.celsius);
  // Temperature(double f) : celsius = (f - 32) * 5 / 9; // ✗
}
output
Error: 'Temperature' is already declared in this scope.

Give the second one a name: Temperature.fromFahrenheit(...).

2. Calling a named constructor that does not exist

dart
class Point {
  final int x;
  final int y;
  Point(this.x, this.y);
}

void main() {
  // var p = Point.origin(); // ✗
}
output
Error: Member not found: 'Point.origin'.

3. Giving a redirecting constructor a body

dart
class Point {
  final int x;
  final int y;
  Point(this.x, this.y);
  // Point.origin() : this(0, 0) { print('origin'); } // ✗
}
output
Error: Redirecting constructors can't have a body.
Try removing the body, or not making this a redirecting constructor.

4. Asking for const from a normal constructor

dart
class Point {
  final int x;
  final int y;
  Point(this.x, this.y);
}

void main() {
  // const p = Point(1, 2); // ✗
}
output
Error: Cannot invoke a non-'const' constructor where a const expression is expected.
Try using a constructor or factory that is 'const'.

Add const to the constructor — if all fields are final.

5. Using this inside a factory

dart
class Student {
  final String name;
  Student(this.name);
  factory Student.fromMap(Map<String, String> map) {
    // print(this.name); // ✗ no object exists yet
    return Student(map['name'] ?? '');
  }
}
output
Error: Expected identifier, but got 'this'.

Best Practices

  • Name constructors after what they take or mean: fromFahrenheit, fromMap, origin.
  • Redirect to the main constructor instead of repeating field assignments.
  • Make a class's constructor const when all its fields are final and its values can be known in advance.
  • Create objects with const wherever the arguments are constant.
  • Use a factory only when you need code before building, or want to return an existing object — otherwise a normal constructor is simpler.

Mini Challenge

Write a Box class with final fields width and height (both int), a const constructor, and a const Box.square(int side) that redirects. Create const Box.square(4) and const Box(4, 4), print the area of the first, and whether the two are identical.

Your program should print exactly:

output
Area: 16
true

Try it yourself before opening the solution.

Show solution
dart
class Box {
  final int width;
  final int height;
  const Box(this.width, this.height);
  const Box.square(int side) : this(side, side);
}

void main() {
  const a = Box.square(4);
  const b = Box(4, 4);
  print('Area: ${a.width * a.height}');
  print(identical(a, b));
}
output
Area: 16
true

How it works

  1. Box.square(4) redirects to Box(4, 4), so both describe the same constant.
  2. Dart keeps one object per constant, so identical is true.

Practice Questions

Work through these in order — they run from beginner to application to challenge.

Beginner

  1. Give a Point class (final int x, y) a named constructor Point.origin() using an initializer list. Print 0, 0.
  2. Add Student.guest() to a Student class with a final String name, setting it to 'Guest'. Print the name.
  3. Make Point's constructor const, create const p = Point(2, 3) and print 2, 3.

Application

  1. Give Rectangle (final int width, height) a redirecting Rectangle.square(int side). Print the area of a square with side 6.

  2. Write Temperature.fromFahrenheit and print the Celsius value of 50 Fahrenheit.

  3. Predict, then run: two const Point(1, 1) objects and two Point(1, 1) objects — what does identical print for each pair?

  4. This class does not compile. Fix it with one keyword so that const Score(90) works:

    dart
    class Score {
      int value;
      const Score(this.value);
    }
  5. Write factory Item.fromMap(Map<String, Object> map) for an Item with name and price (double). Use 'Unknown' and 0 when a key is missing. Print an item built from {'name': 'Pen', 'price': 50.0} and one from {}.

Challenge

  1. Write an unnamed factory Grade(String letter) that returns the same object for the same letter, using a top-level map and a named Grade.create. Print true for two 'A' grades.
  2. Write an Account with a const main constructor, a const Account.empty(String owner) that redirects, and a factory Account.fromMap. Print Ali's account from {'owner': 'Ali', 'balance': 500.0}, then Zayan's empty account.

Practice Answers

Question 1 — Solution

dart
class Point {
  final int x;
  final int y;
  Point(this.x, this.y);
  Point.origin() : x = 0, y = 0;
}

void main() {
  var p = Point.origin();
  print('${p.x}, ${p.y}');
}
output
0, 0

Question 2 — Solution

dart
class Student {
  final String name;
  Student(this.name);
  Student.guest() : name = 'Guest';
}

void main() {
  print(Student.guest().name);
}
output
Guest

Question 3 — Solution

dart
class Point {
  final int x;
  final int y;
  const Point(this.x, this.y);
}

void main() {
  const p = Point(2, 3);
  print('${p.x}, ${p.y}');
}
output
2, 3

Question 4 — Solution

dart
class Rectangle {
  final int width;
  final int height;
  Rectangle(this.width, this.height);
  Rectangle.square(int side) : this(side, side);
}

void main() {
  var square = Rectangle.square(6);
  print(square.width * square.height);
}
output
36

Question 5 — Solution

dart
class Temperature {
  final double celsius;
  Temperature(this.celsius);
  Temperature.fromFahrenheit(double f) : celsius = (f - 32) * 5 / 9;
}

void main() {
  print(Temperature.fromFahrenheit(50).celsius);
}
output
10.0

Question 6 — Solution

dart
class Point {
  final int x;
  final int y;
  const Point(this.x, this.y);
}

void main() {
  print(identical(const Point(1, 1), const Point(1, 1)));
  print(identical(Point(1, 1), Point(1, 1)));
}
output
true
false

Only the const pair shares one canonical object.

Question 7 — Solution

Make the field final:

dart
class Score {
  final int value;
  const Score(this.value);
}

void main() {
  const s = Score(90);
  print(s.value);
}
output
90

Question 8 — Solution

dart
class Item {
  final String name;
  final double price;
  Item(this.name, this.price);

  factory Item.fromMap(Map<String, Object> map) {
    var name = map['name'] as String? ?? 'Unknown';
    var price = map['price'] as double? ?? 0;
    return Item(name, price);
  }
}

void main() {
  var pen = Item.fromMap({'name': 'Pen', 'price': 50.0});
  var blank = Item.fromMap({});
  print('${pen.name}: ${pen.price}');
  print('${blank.name}: ${blank.price}');
}
output
Pen: 50.0
Unknown: 0.0

Question 9 — Solution

dart
final gradeCache = <String, Grade>{};

class Grade {
  final String letter;
  Grade.create(this.letter);

  factory Grade(String letter) {
    return gradeCache.putIfAbsent(letter, () => Grade.create(letter));
  }
}

void main() {
  print(identical(Grade('A'), Grade('A')));
}
output
true

Question 10 — Solution

dart
class Account {
  final String owner;
  final double balance;
  const Account(this.owner, this.balance);
  const Account.empty(String owner) : this(owner, 0);

  factory Account.fromMap(Map<String, Object> map) {
    var owner = map['owner'] as String? ?? 'Guest';
    var balance = map['balance'] as double? ?? 0;
    return Account(owner, balance);
  }
}

void main() {
  var ali = Account.fromMap({'owner': 'Ali', 'balance': 500.0});
  const zayan = Account.empty('Zayan');
  print('${ali.owner}: ${ali.balance}');
  print('${zayan.owner}: ${zayan.balance}');
}
output
Ali: 500.0
Zayan: 0.0

Quick Revision

  1. Why can a class not have two unnamed constructors?
  2. How do you call a named constructor?
  3. What does : this(0, 0) do in a constructor?
  4. What two things must be true of a class for its constructor to be const?
  5. Why is identical(const Point(1, 2), const Point(1, 2)) true?
  6. Is Point(1, 2) constant if the constructor is const?
  7. How is a factory constructor different from a normal one?

Key Takeaways

  • A class has at most one unnamed constructor; named constructors (Point.origin()) add more ways in.
  • Named constructors can use initializer lists to convert values, like Temperature.fromFahrenheit.
  • A redirecting constructor (: this(...)) passes the work to another constructor and has no body.
  • A const constructor needs all fields final and no body; const objects need constant arguments.
  • Each distinct constant exists once: two equal const objects are identical().
  • Without the const keyword at the call, a const constructor builds a new object.
  • A factory runs code and returns an object — a new one or one it saved earlier.

What's Next?

Lecture 56 — this, Getters & Setters. Our objects can now be built in many ways, but their fields are still read and written directly. Next we will look closely at this, and learn how getters and setters let a class compute values and control every change to its data.


Video lecture

Subscribe
Placeholder video — the real recording isn't linked yet.