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 #
finalfield: must be set during object initialization (before the constructor completes), useItem(this.name, this.price)or: name = ..., price = ...late finalfield: 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.