Dart/Flutter Constructors tutorial with examples

eye-catchDart and Flutter

Constructor is one of the basics to implement dart class. There are some features that I didn’t know before writing this article. Let’s learn how to write a constructor with me.

Sponsored links

Dart basic Constructor with mutable members

First of all, let’s check how to write a basic constructor in dart.

The classes in this article always start with the underscore “_”. It means the class is private. Its class can be used only in the same file. If it’s necessary to expose the class to the outside, remove underscore.

class _Point {
  double x = 0;
  double y = 0;
  double? z;

  _Point();
}
// usage
_Point();

There are 3 mutable members. If the constructor required no arguments like this, it doesn’t do anything to those variables in the constructor. If we want to set the value from outside, add arguments and set it to the variable.

class _Point {
  double x = 0;
  double y = 0;
  double? z;

  _Point(double x, double y){
      this.x = x;
      this.y = y;
  }
}
// usage
_Point(1, 1);// [x, y, z] = [1.0, 1.0, null]

The keyword this

The keyword “this” indicates that the variable belongs to the class. We don’t have to add “this” keyword when using a class member if the same variable name doesn’t exist in the clause. “this” keyword should be used only when the same variable name exists in the same clause.

Use “this” keyword only when there is a name conflict. Otherwise, Dart style omits “this”.

The example above is redundant. We can use this keyword for the arguments.

class _Point {
  double x = 0;
  double y = 0;
  double? z;

  _Point(this.x, this.y);
}
// usage
_Point(1, 1); // [x, y, z] = [1.0, 1.0, null]

This behaves the same as the example above.

Multiple constructors in Dart/Flutter

There are some cases that a class wants to provide multiple constructors but it’s not possible to provide them in the same way.

class _Point {
  double x = 0;
  double y = 0;
  double? z;

  _Point();
  _Point(this.x, this.y);   // error
}

Instead, a class needs to provide a named constructor. The syntax is as follows.

<class name>.<constructor name>(){}

This is an example.

class _Point {
  double x = 0;
  double y = 0;
  double? z;

  _Point();
  _Point.create(double x, double y) {
    this.x = x;
    this.y = y;
  }
}
// usage
_Point.create(1, 1); // [x, y, z] = [1.0, 1.0, null]

We can define multiple functions that require the same parameters.

class _Point {
  double x = 0;
  double y = 0;
  double? z;

  _Point();
  _Point.create(double x, double y) {
    this.x = x;
    this.y = y;
  }
  _Point.power(double x, double y) {
    this.x = x * x;
    this.y = y * y;
  }
  _Point.create2(this.x, this.y);
}
// usage [x, y, z]
_Point.create(1, 1);    // [1.0, 1.0, null]
_Point.power(5, 5);     // [25.0, 25.0, null]
_Point.create2(1, 1);   // [1.0, 1.0, null]

Dart/Flutter constructor with optional parameters

Dart Constructor using curly braces: Named optional

We need to check the parameter order to set the proper value to the constructor with the examples that we saw so far. Some of you might want to see the parameter name in your caller code. Let’s use curly braces in this case.

class _Point {
  double x = 0;
  double y = 0;
  double? z;

  _Point.named({
    required this.x,
    required this.y,
    this.z,
  });
}

The variables with “required” keyword are mandatory parameters. A parameter without the keyword is an optional parameter.

_Point.named(x: 1, y: 1);         // [1, 1, null]
_Point.named(x: 1, y: 1, z: 1);   // [1, 1, 1]

!! Optional parameter without default value must be nullable type.

With this named constructor, we don’t have to sort the parameters because each argument has its own parameter name. We can set the parameters in any order.

_Point.named(y: 1, x: 2);         // [2.0, 1.0, null]
_Point.named(y: 1, z: 2, x: 3);   // [3.0, 1.0, 2.0]

Dart constructor using square brackets: Positional optional parameters

If we want to set parameters with a fixed order, define the parameters with brackets.

class _Point {
  double x = 0;
  double y = 0;
  double? z;

  _Point.positional(this.x, this.y, [this.z]);
}
// usage [x, y, z]
_printPoint(_Point.positional(1, 2));      // [1.0, 2.0, null]
_printPoint(_Point.positional(1, 2, 3));   // [1.0, 2.0, 3.0]

It doesn’t require a parameter name.
As I explained above, the optional parameter must be nullable. If we want to make “y” optional we need to set a value in the constructor because it is not a nullable data type.

_Point.positional2(double x, [double? y, double? z]) {
  this.x = x;
  this.y = y ?? 0;  // set a value
  this.z = z ?? 0;
}

Dart/Flutter Constructor default value

If we want to assign a default value to the optional parameters, we need to use either named constructor or brackets constructor.

class _Point {
  double x = 0;
  double y = 0;
  double? z;
  _Point.named2({
    required this.x,
    this.y = 10,
    this.z = 5,
  });
  _Point.positional3(double x, [double y = 0, double z = 0]) {
    this.x = x;
    this.y = y;
    this.z = z;
  }
}
// usage [x, y, z]
_Point.named2(x: 1);    // [1.0, 10.0, 5.0]
_Point.positional3(1);  // [1.0, 0.0, 0.0]

Redirecting the constructor

Sometimes, we want to call another constructor in a constructor. You firstly might want to write the constructor call in it like this.

class _Point {
  double x = 0;
  double y = 0;
  double? z;

  _Point.origin() {
    _Point.create4(0, 0, 0);
  }
}
// Usage [x, y, z]
_Point.origin(); // [0.0, 0.0, null]

The z is null. It doesn’t work as expected. The new instance is created in the constructor but it doesn’t affect the result of the _Point.origin() constructor call.

The constructor call needs to be written in the following way.

class _Point {
  double x = 0;
  double y = 0;
  double? z;

  _Point.create4(this.x, this.y, this.z);
  _Point.origin2() : this.create4(0, 0, 0);
}
// Usage [x, y, z]
_Point.origin2(); // [0.0, 0.0, 0.0]

z is initialized correctly.
By the way, it can’t have the body in the redirecting constructors as you see the following error message.

// Redirecting constructors can't have a body.
// Try removing the body, or not making this a redirecting constructor.
_Point.origin3() : this.create4(0, 0, 0){
  print("");
}

Constructor initializer list

How can we write if we want to set a value that uses a specified value + alpha? Use the initializer list in this case.

class _Point {
  double x = 0;
  double y = 0;
  double? z;

  _Point.initializer(double value)
      : x = value + 1,
        y = value + 2,
        z = value * value;
}
// Usage [x, y, z]
_Point.initializer(9);  // [10.0, 11.0, 81.0]

Another instance member can’t be used to initialize a member. This rule is applied to all constructors.

_Point.initializer2(double value)
    : x = value + 1,
      y = value + 2,
      z = y;
// The instance member 'y' can't be accessed in an initializer.
// Try replacing the reference to the instance member with a different expression

If y is a class instance and we want to assign the instance to z, we need to consider a different way. Write the operation in the body in this case.

  _Point.initializer(double value)
      : x = value + 1,
        y = value + 2 {
    z = y;
  }

Dart Constructor with immutable/final members

A class member is not always mutable. The variable has the “final” keyword in this case. We can’t assign a value to the variable in the constructor body in this case.

class _Line {
  final _Point startPoint;
  final _Point endPoint;
  _Line.horizontal({
    required this.startPoint,
    required double length,
  }) {
    // 'endPoint' can't be used as a setter because it's final.
    // Try finding a different setter, or making 'endPoint' non-final.
    endPoint = _Point.create(length, 0);
  }
}

The variable with the final keyword doesn’t have a setter. Therefore, it’s impossible to assign a value in the body. If we want to assign a value in the body or somewhere else before actually using it, the “late” keyword can be used.

class _Line {
  final _Point startPoint;
  late final _Point endPoint;
  _Line.horizontal({
    required this.startPoint,
    required double length,
  }) {
    endPoint = _Point.create(length, 0);
  }
}
// Usage
_Line.horizontal(startPoint: start, length: 5); // [(0.0, 0.0), (5.0, 0.0)]

If the variable has the “late” keyword, a setter is added to the variable but the setter can be used only once. Let’s try to assign a value again in a function.

class _Line {
  ...
  initEndPoint() {
    // runtime error!!
    endPoint = _Point.origin2();
  }
}
final instance = _Line.horizontal(startPoint: start, length: 5);
instance.initEndPoint();
// Unhandled exception:
// LateInitializationError: Field 'endPoint' has already been initialized.

We got a runtime error here. Since it’s not a compile-time error, we might assign a value again in real work. Be careful and make sure that the value is assigned only once.

Factory Constructor in Dart/Flutter

We can use also the factory keyword for a constructor. The use-case is the following.

  • return an instance from a cache
  • return instance of a subtype
  • initializing a final variable with logic

For factory constructors, a class member can’t be used in the argument list.

class _Line {
  final _Point startPoint;
  late final _Point endPoint;
  // Initializing formal parameters can't be used in factory constructors.
  // Try using a normal parameter.
  factory _Line.vertical(this.startPoint) {
    ...
  }
}

This example is for the third point. Initializing with logic.

class _Line {
  final _Point startPoint;
  late final _Point endPoint;
  _Line._internal(this.startPoint, this.endPoint);
  factory _Line.vertical(double length) {
    final start = _Point.create(0, 0);

    if (length > 10) {
      length = 10;
    }
    final end = _Point.create(0, length);
    return _Line._internal(start, end);
  }
}

Constant constructor

If a class has only immutable members, the “const” keyword can be used to initialize.

class _ImmutableDataHolder {
  final int uid;
  final String name;
  const _ImmutableDataHolder({
    required this.uid,
    required this.name,
  });
}

If the class has a non-final variable, an error message is shown.

class _ImmutableDataHolder {
  final int uid;
  String name;
  // Can't define a const constructor for a class with non-final fields.
  // Try making all of the fields final,
  // or removing the keyword 'const' from the constructor.
  const _ImmutableDataHolder({
    required this.uid,
    required this.name,
  });
}

If the const keyword is used to initialize the instance, the constructor returns always the same instance. It’s constant. If we want to have a different instance for some reason, we can get a new instance if we don’t add the const keyword.
See the following example.

var first = const _ImmutableDataHolder(uid: 1, name: "yuto");
var second = const _ImmutableDataHolder(uid: 1, name: "yuto");
var third = _ImmutableDataHolder(uid: 1, name: "yuto");
print(identical(first, second)); // true
print(identical(first, third)); // false
print("first:  ${first.hashCode},\n"
    "second: ${second.hashCode},\n"
    "third:  ${third.hashCode}");
//  first:  775314292,
//  second: 775314292,
//  third:  108382137

The first and second variables are exactly the same but the third one is different from those two instances because the const keyword is not used for the third variable.

Check the following article if you don’t know what identical function is.

End

Did you know all ways explained in this article?

Check the following article as well if you need to create a subclass.

Comments

Copied title and URL