Dart Final Fields

Summary: In this tutorial, you’ll learn about Dart final fields and how to initialize them properly in the class.

Introduction to Dart final fields #

In Dart, a final field:

  • Can be only assigned once.
  • And must be assigned before the constructor completes.

In other words, by the time the constructor finishes, every final field must have a value, and that value can never change afterward.

Typically, a constructor has two phases:

Phase 1: Initialize fields #

Dart runs phase 1 before the constructor body. It happens using:

1) Field initializers:

class Item {
  final price = 10;
}
Code language: Dart (dart)

2) Initialzer list:

class Item {
  final double price;
  Item(double price): price = price;
}Code language: Dart (dart)

3) Constructor shorthands:

class Item {
  final double price;
  Item(this.price);
}Code language: Dart (dart)

At the end of phase 1, Dart must initialize all final fields.

Phase 2: Run the constructor body #

Dart runs the code inside the constructor after it completes the field initialization.

The reason Dart requires this is that final fields are guaranteed once the object is created, and its final fields are immutable. Additionally, other code can safely read final fields knowing that they won’t be missing or changed later.

So if you have a class with final fields, you must not initialize their values inside the constructor’s body, like in this example:

class Item {
  final String name;
  final double price;

  Item(String name, double price) {
    this.name = name;
    this.price = price;
  }
}Code language: Dart (dart)

This example causes the following error:

All final variables must be initialized, but 'name' and 'price' aren't.Code language: Dart (dart)

To fix this, you need to use either the shorthand constructor:

class Item {
  final String name;
  final double price;

  Item(this.name, this.price);
}
Code language: Dart (dart)

Or initializer list:

class Item {
  final String name;
  final double price;

  Item(String name, double price) : name = name, price = price {
    // constructor body
  }
}Code language: Dart (dart)

Late final fields #

The late final field change the rule so that it must be assigned before it is read, not before the constructor completes.

So you can create an object with late final The field has not set yet. But if you read the value of the late final field, you’ll get a runtime error.

The late final A field is useful when you need a value to be assigned exactly once, but not necessarily inside the constructor’s initializer phase.

For example:

class Report {
  final List<int> data;
  Report(this.data);

  // Expensive computation
  late final double average = _computeAverage();

  double _computeAverage() {
    print('Computing average...');
    return data.reduce((a, b) => a + b) / data.length;
  }
}

void main() {
  final r = Report(List.generate(1_000_000, (i) => i));

  print('Created report');
  // average not computed yet

  print(r.average); // computed here, once
  print(r.average); // cached, no recomputation
}Code language: Dart (dart)

Output:

Created report
Computing average...
499999.5
499999.5Code language: Dart (dart)

In this example, the average is computed once. It is deferred and cached after the first access.

Summary #

  • final field: must be set during object initialization (before the constructor completes), use Item(this.name, this.price) or : name = ..., price = ...
  • late final field: can be set later, but must be set before first access. You’ll encounter a runtime error if you read it before the value is set.
Was this tutorial helpful ?