Skip to content

Beginner · Null safety and object-oriented Dart · topic 6 of 50 · 35 min

Classes and constructors

Explanation

A class groups data (fields) and behaviour (methods). Dart constructors can use this.field shorthand, named constructors such as Product.fromJson, initializer lists, const constructors for compile-time constant objects, and factory constructors that can return cached or subclass instances. A name starting with _ is private to its library (file). Getters and setters look like fields but run code.

Why classes?

Apps deal with things: products, orders, users. A class describes one kind of thing: what data it has (fields) and what it can do (methods). An object (instance) is one actual thing created from the class. Every Flutter widget is a class too, so understanding classes is essential.

dart
class Product {
  final String name;
  final int pricePaise;
  int stock;

  Product(this.name, this.pricePaise, this.stock);      // this.field shorthand

  bool get inStock => stock > 0;                         // getter
  String get priceText => 'Rs ${pricePaise ~/ 100}';

  void sell(int qty) {
    if (qty > stock) throw StateError('Only $stock left');
    stock -= qty;
  }

  @override
  String toString() => 'Product($name, $priceText, stock: $stock)';
}

void main() {
  final p = Product('Steel bottle', 49900, 3);
  p.sell(2);
  print(p);                 // Product(Steel bottle, Rs 499, stock: 1)
  print(p.inStock);         // true
}
No new keyword is needed (or recommended) to create objects in Dart.

Fields marked final must be set once, in the constructor, and never change; that makes objects predictable. Plain fields (like stock above) can change. A getter such as inStock has no stored value: it is calculated every time you read it, so it can never be out of date.

Named constructors

A class can have several constructors with different names: ClassName.something(...). They are very common for creating objects from JSON or with preset values.

dart
class Product {
  final String name;
  final int pricePaise;

  Product({required this.name, required this.pricePaise});

  Product.free(this.name) : pricePaise = 0;              // named + initializer list

  factory Product.fromJson(Map<String, dynamic> json) {
    return Product(name: json['name'] as String, pricePaise: json['price'] as int);
  }
}
The part after : is an initializer list: it sets fields before the constructor body runs.

const constructors

If all fields are final and the constructor is marked const, you can create compile-time constant objects with const. Identical const objects are created once and shared. Flutter widgets use this a lot: const Text('Hello') and const SizedBox(height: 8) let Flutter skip rebuilding parts of the screen.

dart
class Spacing {
  final double value;
  const Spacing(this.value);
}

void main() {
  const a = Spacing(8);
  const b = Spacing(8);
  print(identical(a, b));     // true: one shared object
}
identical checks whether two variables refer to the very same object.

factory constructors

A normal constructor always creates a new object of exactly that class. A factory constructor can run code first and return an existing object (a cache), or an object of a subtype. fromJson constructors are often factories because they parse and validate before creating.

Privacy with _

Dart has no public, private or protected keywords. A name that starts with an underscore (_balance, _calculate) is private to its library, which normally means its file. Code in other files cannot see it.

dart
class Wallet {
  int _balance = 0;                // private field
  int get balance => _balance;     // read-only from outside

  void add(int amount) {
    if (amount <= 0) throw ArgumentError('amount must be positive');
    _balance += amount;
  }
}
Others can read balance but must use add to change it, so the rule is always checked.
Think of it like this: A class is like a printed form for a bank account. The fields are the boxes, the constructor is filling in the form, named constructors are pre-filled versions ("student account", "senior citizen account"), and private fields are the bank's internal boxes customers cannot write in.

Common mistakes

  • Forgetting to initialise final fields: "must be initialized". Use this.field in the constructor or an initializer list.
  • Making fields public and mutable everywhere. Prefer final fields and methods that check the rules.
  • Expecting _private to mean private to the class. It is private to the file.
  • Missing const on constructors of simple immutable classes, losing the performance benefit in Flutter.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Classes and constructors · 1/6

final p = Product('Bottle', 49900, 3);

A class groups fields and methods. Objects are created without the new keyword.

Prefer a video by a teacher? Find free videos on “dart classes constructors named factory const initializer list” on YouTube ↗

In real life: A product model from an API

A shopping app receives products as JSON. The Product class has final fields, a const constructor for tests and previews, a factory Product.fromJson that reads and checks the map, and getters for formatted prices. Every screen uses the same class, so formatting and validation are consistent.

dart
class Product {
  final int id;
  final String name;
  final int pricePaise;
  const Product({required this.id, required this.name, required this.pricePaise});

  factory Product.fromJson(Map<String, dynamic> j) =>
      Product(id: j['id'] as int, name: j['name'] as String, pricePaise: j['price_paise'] as int);

  String get price => 'Rs ${(pricePaise / 100).toStringAsFixed(2)}';
}

void main() {
  final p = Product.fromJson({'id': 7, 'name': 'Kurta', 'price_paise': 89900});
  print('${p.name}: ${p.price}');      // Kurta: Rs 899.00
}
Map<String, dynamic> is the usual type of decoded JSON objects.

Practice (0/3 done)

Task 1 · Question

What does this print? class Box { final int w, h; const Box(this.w, this.h); int get area => w * h; } void main() => print(Box(3, 5).area);

Task 2 · Question

Which constructor kind can return an existing cached object instead of a new one?

Task 3 · Practice

Write a class BankAccount with a private _balance, a required named holder, a getter balance, deposit and withdraw methods that throw on invalid amounts or insufficient balance, a named constructor BankAccount.zero(holder), and a toString. Use it in main, including a failed withdrawal caught with try/catch.

Quick quiz

Question 1

1.How do you make a field private in Dart?

Question 2

2.What is an initializer list?

Question 3

3.Why do Flutter widgets often use const constructors?

Question 4

4.What does identical(a, b) check?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

How do you make a field private in Dart?

Answer: Start its name with _

Underscore names are library-private.

What is an initializer list?

Answer: Code after : in a constructor that sets fields before the body runs

For example : pricePaise = 0.

Why do Flutter widgets often use const constructors?

Answer: Constant widgets are created once and can be skipped during rebuilds

const saves work at run time.

What does identical(a, b) check?

Answer: Whether a and b are the very same object

Identity, not equality.

What does : this(name, 0m) after a constructor header do?Related

Answer: Calls another constructor of the same class first

It chains to another constructor to reuse its setup code.

From Classes, objects and constructors

What makes a class field private?Related

Answer: A leading #

#name fields are truly private; _name is only a naming convention.

From Classes

What is the return type of a constructor?Related

Answer: It has none

Constructors have no return type, not even void.

From Constructors and this

Which constructor type can return a cached instance?Related

Answer: factory

Factories decide what to return.

From Interview questions: Dart

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 2 more topics in “Null safety and object-oriented Dart” before its exam: Inheritance, interfaces, mixins and extensions, Dart 3: enums, records, patterns and sealed classes.
  2. Module 2 exam: Null safety and object-oriented Dart · 13 questions · 25 min, without help. 4 topics of this module still to finish first.
  3. Then the next module, Async Dart and packages. 50 topics left in the course before the final exam.

← Previous: Sound null safety