1

I'm new to Dart, and I was learning about Classes in Dart.

I'm facing a problem understanding how constructors in Dart actually work.

I was used to initialize fields within constructors in the following way with other programming languages, but in Dart I get many warnings and errors with the following way of initializing fields.

class Car {
  String brand;
  String model;
  int year;
   
   Car(String brand, String model, int year) {
      this.brand = brand;
      this.model = model;
      this.year = year;
   }
}

I think that the problem is concerned with the null safety in Dart, but I don't want to use the ? operator or the keyword late at the time of the fields declaration.

So, could anyone tell me how I could initialize the fields properly using constructors in Dart?

Thanks

Noobinator
  • 43
  • 6
  • [How do I initialize non-nullable members in a constructor body?](https://stackoverflow.com/q/66725613/) – jamesdlin Feb 16 '23 at 22:49

2 Answers2

1

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.

Levi Lesches
  • 1,508
  • 1
  • 13
  • 23
0

Instance fields must either have an initializer at the declaration, use an initializing formal, or be initialized in the constructor's initialization list. Here are the examples:

// initializer at the declaration
class Car {
  String brand = 'Brand';
  String model = 'Model';
  int year = 2023;

  Car();
}

// initializing formal
class Car {
  String brand;
  String model;
  int year;

  Car(this.brand, this.model, this.year);
}

// constructor's initialization list
class Car {
  String brand;
  String model;
  int year;

  Car(String brand, String model, int year)
      : this.brand = brand,
        this.model = model,
        this.year = year;
}

For more information and better understanding why dart works in that way please read the official documentation.

Vladimir Gadzhov
  • 273
  • 2
  • 10