The problem is that the constructor body is essentially just a function. Which means you can do anything that a function can normally do. Including this:
class Car {
String brand;
String model;
int year;
void printCarDetails() => print("This car is from $year");
Car(String brand, String model, int year) {
printCarDetails(); // <-- what should this print?
this.brand = brand;
this.model = model;
this.year = year; // <-- if [year] is only initialized here?
}
}
The problem is that you can use fields before you've initialized them (remember that int
isn't null
by default -- int?
is). Dart needs a section before the constructor body where you can guarantee you've initialized everything, and then you can run your constructor body as normal.
That section is called an initializer list:
class Car {
String brand;
String model;
int year;
Car(String brand, String model, int year) :
brand = brand,
model = model,
year = year
{
printCarDetails();
}
void printCarDetails() => print("This car is from $year");
}
So everything after the :
is just initializing fields, and then everything in the { }
is the constructor body, which can then access those fields.
But the initializer list is ugly to look at. Thankfully, Dart provides a shorthand called initializing formal parameters:
class Car {
String brand;
String model;
int year;
Car(this.brand, this.model, this.year) {
// brand, model, and year are all initialized now
printCarDetails();
}
void printCarDetails() => print("This car is from $year");
}
This is the idiomatic way of initializing fields, and you should try to stick to this pattern when you can.
As a side note, you may run into this problem when using the constructor of a super class:
class Vehicle {
String brand;
Vehicle(this.brand);
}
class Car extends Vehicle {
String model;
// Passes [model] to the initializer list, and [numWheels] to Vehicle
Car(this.model, super.numWheels);
}
This feature is called super parameters, and it is relatively new.