Constructor in Dart

What is Constructor?

The constructor is to create and initialize an objects of classes.

OR

A constructor is used in instance creation to obtain objects of classes.

A constructor name always begins with the name of its class.

The following code snippet shows how to define a constructor with parameters and initialize an objects of class.

class Person {

  // Person Constructor with parameter.
  Person(String name) {}
}

main() {
  var person = Person('Rock'); //
}

Default Constructor

The default constructor has no arguments and no name.

If no constructor is specified for a class, it implicitly has a default constructor, that is used in instance creation.

() operator use to define the constructor for instance creation.

The following code snippet show the default construct of a class.

class Person {}

main() {
  var person = Person();
}

Initializer Lists

The initializer list is to invoke superclass constructor, and also initialize variables before the constructor body runs.

Initializer lists are handy when setting up final fields.

import 'dart:math';

class Point {
  final num x;
  final num y;
  final num distanceFromOrigin;

  Point(x, y)
      : x = x,
        y = y,
        distanceFromOrigin = sqrt(x * x + y * y);
}

main() {
  var p = new Point(2, 3);
  print(p.distanceFromOrigin);
}

The right-hand side of an initializer does not have access to this keyword.

Scenario

In the case you don't want to expose your variables defined in Point and your mark those as private, the initializer would be a good option.

Redirecting Constructors

The redirecting constructor is to only redirect to invoke another constructor in the same class.

A redirecting constructor has no body; instead, it has a redirect clause that specifies which constructor the invocation is redirected to, and with which arguments. The constructor call appearing after a colon (:).

The following code snippet shows how th redirect constructor, which invokes default constructor.

class Skill {
  String name;

  String level;

  Skill(this.name, this.level);

  Skill.onlySkill(String name) : this(name, '');
}

main() {
  var skill = Skill.onlySkill('Angular');

  print(skill.name); // Angular
}

Factory Constructors

The factory constructor is that doesn’t always create a new instance of its class.

A factory constructor can produce instances that are not freshly allocated:

  1. You want to return an instance from a cache.
  2. Or you want to return an instance of a subclass.

Factories can return instances of different classes

It have several advantages over direct object instantiation, such as:

  1. Hiding the details of instantiation.
  2. Providing the ability to return a subclass of the factory's return type.
  3. And optionally returning an existing object rather than a new object.

Factory constructor either unnamed or named.

To create a factory constructor, use the factory keyword.

class Logger {

  // instance variables or properties
  final String name;
  bool mute = false;

  // class variable or property
  static final Map<String, Logger> _cache = <String, Logger>{};

  // Declare unnamed factory constructor using factory keyword
  factory Logger(String name) {
    return _cache.putIfAbsent(name, () => Logger._internal(name));
  }

  // Named generative private constructor
  Logger._internal(this.name);

  // instance method
  void log(String msg) {
    if (!mute) print(msg);
  }
}

main() {
  var logger = Logger('Update failed');
  logger.log('Update failed, please try later');
}

Return an instance of a subclass

The following example, shows the factory constructor returns the instance of a subclass Employee.

class Person {
  String name;
  bool isEmployee;

  Person(this.name, this.isEmployee);

  // named factory
  factory Person.employeeOnly(String name) {
    return Employee(name);
  }
}

class Employee extends Person {
  Employee(String name) : super(name, true);
}

main() {
  var person = Person.employeeOnly('Rock star');

  print(person.isEmployee);
}

Constant Constructors

The constant constructor is to make class objects compile time constants.

If your class produces objects that never change, you can make these objects compile-time constants.

Put the const reserved keyword before the constructor name.

Declare constant constructor:

  1. Put the const reserved keyword before the constructor name while defining a constant constructor.
  2. Make sure that all instance variables are final.

The following code snippet shows how to define constant constructor.

class Person {
  // Make all instance variable are final
  final String firstName;
  final String lastName;

  // All final variables must be initialized,

  // Define const constructor using const keyword
  const Person(this.firstName, this.lastName);
}

Use const constructor:

  1. Create an instance of the class.
  2. Pass 'firstName' and 'lastName' as a constructor arguments.
  3. Get the 'firstName' and 'lastName' values and print to the console.
main() {
  var person = Person('Rock', 'Star');
  print(person.firstName);
  print(person.lastName);
}

Note: Instance variables 'firstName', 'lastName' can't be used as a setter because it is final.

main() {
  var person = Person('Rock', 'Star');
  // 'firstName' can't be used as a setter because it is final.
  person.firstName = "Oorja"; //Error
}

Note: Constant constructors don’t always create constants.

this keyword

The this keyword is to refer to the current instance.

Use this keyword to assigning a constructor argument to an instance variable.

Use this.propertyName when declaring the constructor:

class Employee {
  String firstName;
  String lastName;

  Employee(this.firstName, this.lastName);
}

main() {
  var employee = Employee('Rock', 'Star');
}

this keyword with named parameters

The following snippet shows how to use this keyword is named parameters.

this keyword works for named parameters, too. Property names become the names of the parameters:

class Employee {
  String firstName;
  String lastName;

  // Name parameters
  Employee({this.firstName, this.lastName});
}

main() {
  var employee = Employee(firstName: 'Rock', lastName: 'Star');
}

this keyword with positional parameters

The following code snippet shows how to use this keyword with positional parameters.

You can also use this with constructors positional parameters.

class Employee {
  String firstName;
  String lastName;

  // Positional parameters
  Employee([this.firstName, this.lastName]);
}

main() {
  var employee = Employee('Rock', 'Star');
}

Default values with this keyword

The following code snippet shows how this keyword used with named and positional parameters wit default values.

For optional parameters, default values work as expected:

// Positional parameters
  Employee([this.firstName = '', this.lastName = '']);

  // or

  // Named parameters
  Employee({this.firstName = '', this.lastName = ''});

Named Constructors

The named constructor is to create multiple constructors for a class.

The class can have only one unnamed constructor, but it can have any number of named constructors to allow classes to have multiple constructors.

The following code snippet shows hot to use a named constructor to implement multiple constructors for a class or to provide extra clarity.

class Employee {
  String firstName;

  String lastName;

  Employee(this.firstName, this.lastName);

  // Named Constructor
  Employee.fromEmployee(Employee employee) {
    firstName = employee.firstName;
    lastName = employee.lastName;
  }
}

Private Named Constructors

Named constructors can also be private by starting the name with _

The following code snippet shows how to create private constructor, which accessible only from inside the class itself.

Employee._(this.firstName, this.lastName);

  // private named constructor
  Employee._fromEmployee(Employee employee) {
    firstName = employee.firstName;
    lastName = employee.lastName;
  }

NoSuchMethod

The NoSuchMethod is to invoke when a non-existing method or property is accessed.

Invoked when a non-existent method or property is accessed.

The noSuchMethod() is invoked implicitly during execution in situations whenever code attempts to use a non-existent method or instance variable.

Override noSuchMethod

You can override noSuchMethod in your class to provide custom behavior.

NoSuchMethodError

Accessing non-existent member results in a NoSuchMethodError. The default behavior is to throw a NoSuchMethodError.

class Person {}

main() {
  dynamic person = Person();

  person.fullName(); // NoSuchMethodError: Class 'Person' has no instance method 'fullName'.
}Override `noSuchMethod()` to avoid error and provide custom behavior.

```dart
class Person {
  
  noSuchMethod(Invocation invocation) {
    print('You tried to access a non-existent member: ' +
        '${invocation.memberName}');
  }
}

main() {
  dynamic person = Person();

  person
      .fullName(); // You tried to access a non-existent member: Symbol("fullName")
}

Overriding Members

The Overriding members is to override default behavior or state, to provide specific behavior or state.

Subclasses can override instance methods, getters, and setters. You can use the @override annotation to indicate that you are intentionally overriding a member.

class Customer {
  double Discount() {
    return 2.5;
  }
}

class RetailCustomer extends Customer {
  
  double Discount() {
    return 5.0;
  }
}

class WholeSaleCustomer extends Customer {
  
  double Discount() {
    return 7.5;
  }
}

main() {
  var retailCustomer = RetailCustomer();

  print('The discount for retail customers ${retailCustomer.Discount()} %');

  var wholeSaleCustomer = WholeSaleCustomer();

  print(
      'The discount for whole sate customers ${wholeSaleCustomer.Discount()} %');
}

18 Overriding Operator

The Overriding operator is to provide the custom implementation of an operation in case one or both of the operands are of that type.

You can override the operators.

Operators are instance methods with special names. An operator declaration is identified using the built-in identifier operator.

The following example,shows of a class that overrides the + and - operators

class Vector {
  final int x, y;

  Vector(this.x, this.y);

  Vector operator +(Vector v) => Vector(x + v.x, y + v.y);

  Vector operator -(Vector v) => Vector(x - v.x, y - v.y);
}

void main() {
  final v = Vector(2, 3);

  final w = Vector(2, 2);

  assert(v + w == Vector(4, 5));

  assert(v - w == Vector(0, 1));
}