Constructors
Learning objectives
- You practice working with classes and objects in Dart.
- You know how to define different types of constructors.
Constructors
In all of the prior examples, we used a constructor that sets instance property values automatically, at least from the perspective of e.g. Java programmers (prior to the introduction of record classes). There was no explicit need to define a constructor block to assign the instance property values.
When we write a constructor that follows the form Name(this.property, this.another, ...)
, we override a default constructor of the class. Each class can have only one default constructor, and defining multiple constructors in the said format leads to an error. For example, the following class would not work.
Named constructors
Classes can have multiple constructors, but only one default constructor (that shares the name of the class). Additional constructors can be created as named constructors, where each constructor has a specific name. Creating a named constructor is done by providing the class name followed by a dot and the name for the constructor, which is then followed by the parameters.
In the following example, we define a named constructor unknownBirthYear
to the class Person
, where the birth year would then be set to -9999
upon creation -- the named constructor is followed by a colon, which is followed by initialization of properties that are not directly initialized in the constructor.
Let's look at another class that is used to define tasks. Tasks have names and tasks can be completed. The basic implementation of such a class could be as follows.
Evaluations in String interpolation
The above example also shows how string interpolation can be imbued with statements that are evaluated. When using the $
-syntax in a string, you can define a block after the $-sign that will then be evaluated.
In some cases, it might be the case that the task is already completed, and one might prefer to have a separate constructor for such a case. To have a task in a completed state from the start, we would define a named constructor called completed
that takes the name as a parameter and sets the instance property completed
as true
.
As you notice, the constructor completed
is followed by a colon, which is followed by initialization of properties. If there would be more properties that we would wish to initialize, we would separate them by commas, as demonstrated in the following example with a constructor called homework
.
Redirecting constructors
Dart also allows using another constructor from constructors (called "redirecting constructors" in Dart). The constructors of the above class could also be written as follows.
Default instance property values
It is also possible to define default values for instance properties. This is done by providing them in the class definition -- these values are then overridden by constructors. In the following example, the value for completed
would be false
by default. In this case, we would not have to explicitly state that the value of completed
is false
in the named constructor homework
.
Code in constructors
The previously used overridden default constructor and named constructor did not have code that would define the behavior of the constructors. In some cases, we wish to be able to have logic in the constructors as well. It is possible to add code blocks to constructors, which then define the logic of the constructors.
The following example demonstrates the use of such logic. The named constructor fromMap
takes in a map as a parameter and creates an object using values in the map.
class Task {
String name;
bool completed = false;
Task(this.name, this.completed);
Task.completed(this.name) : completed = true;
Task.homework() : name = "Finish homework";
Task.fromMap(Map data)
: name = data["name"],
completed = data["completed"];
String toString() {
return "[${completed ? "x" : " "}] $name";
}
}
Now, we can create a task object from a map.
Named arguments
Named arguments allow a way to define the arguments that are passed values when calling constructor; this makes the use of specific arguments more explicit. In the following example, we adjust the named constructor completed
of the class Task
so that it takes the name of the task as a named argument -- we also define a default name "Unknown" for the task.
Now, when creating a completed task, we need to be explicit about the name of the argument that we are passing values into.
When defining a constructor, named arguments are defined within curly brackets. When calling a constructor, values for named arguments follow after the names.
Required named arguments
Named arguments must either have a default value (such as "Unknown" above) or they must be set as required
. In the case of a default value, one can use the a named constructor with named arguments also without explicitly passing values to the constructor. An as example, with the above code, the following task would have the name "Unknown".
main() {
final task = Task.completed();
print(task);
}
When defining named arguments, provide them default values or set the arguments as
required
.
In the following example, the named constructor completed
has a required
named property. Now, calling the named constructor completed
without a passing the value for the argument name
would lead to an error.