Dart is not only a programming language that supports Object Oriented Programming (OOP), it is also similar to other programming languages such as Java or C++ in its implementation.

We can create a class by using the class keyword.

Starting from the version 2.12, Dart is null safe by default. That means a variable cannot contain a null value unless we ask Dart to allow a null value stored in a variable explicitly.

This rule applies to classes also. When we create a class, we must initialize class properties or tell Dart that some variable can contain a null value.

example:

class Bottle {
  String color = 'blue';
  String material = 'Copper';
  int capacity = 1;
}

With this type of class, each object we create based on it will have the same color, material and capacity. These type of objects may not need to have properties whose values are unique to that object.

In most of the real time applications the objects needs to be unique from each other. To create such dynamic objects we can use constructor functions.

Class Constructor

A class constructor is a special type of function which gets executed automatically whenever we create an Object based on that class.

In some programming languages like JavaScript, ‘constructor‘ will be the name of the constructor function. In Dart, a function with the same name as that of class will act as constructor function.

example:

class Mobile {
  late String id;
  late String modelName;
  late int releaseYear;
  Mobile( uid, mName, rYear ) {
    id = uid;
    modelName = mName;
    releaseYear = rYear;
  }
}

Mobile m1 = Mobile('abcd1234', 'XYZ', 2021);
Mobile m2 = Mobile('abcd4321', 'XYZ', 2022);

In the above example, we are passing data to the constructor function which in turn uses it to assign to the properties of that class. Many programming languages requires new keyword while creating an object. Though we can use new keyword to create objects in Dart also, it is not recommended and we almost never use it.

We used late keyword before property names to tell Dart that, these properties are not going to be null, but we will initialize them later.

The previous example is however is not considered as a good practice. Dart recommends not using the late keyword when we are initializing the variables through constructor. Instead of assigning values like in previous example, it is recommended to use Initializer List.

example:

class Mobile {
  String id;
  String modelName;
  int releaseYear;
  Mobile(uid, mName, rYear)
      : id = uid,
        modelName = mName,
        releaseYear = rYear;
}

Mobile m1 = Mobile('abcd1234', 'XYZ', 2021);
Mobile m2 = Mobile('abcd4321', 'XYZ', 2022);

void main() {
  print(m1.id);
  print(m2.id);
}

In the above example, we did not use function body to assign the received values to the properties. We used colon (:) followed by a list of assignments separated by commas.

If you want to allow a null value to any property of a class, then we can use a question mark (?) after the data type like this

class Mobile {
  String? id;
  String? modelName;
  int? releaseYear;
  Mobile( uid, mName, rYear ) {
    id = uid;
    modelName = mName;
    releaseYear = rYear;
  }
}

If your property names can be exposed then we can directly assign the values to the properties like this

class Mobile {
  String id;
  String modelName;
  int releaseYear;
  Mobile(this.id, this.modelName, this.releaseYear);
}

Mobile m1 = Mobile('abcd1234', 'XYZ', 2021);
Mobile m2 = Mobile('abcd4321', 'XYZ', 2022);

Access Modifiers in Dart

All properties and methods in a Dart class are public and there is no concept of private or protected fields in Dart.

A file with ‘.dart‘ extension is considered as a library in Dart. We can import and export these libraries in an application.

If you have a library with a class containing a property whose name starts with an underscore (_), that property will be available only inside that file(library). That property will not be available in other libraries where you imported that library.

Named Constructors

Dart do not have the concept of method overloading. That means we cannot have two methods with same name but different signature. Instead of method overloading, Dart offers Named Constructors.

We can have multiple constructors with different signatures like this

example:

class Mobile {
  late String id;
  late String modelName;
  late int releaseYear;
  Mobile(uid, mName, rYear) {
    id = uid;
    modelName = mName;
    releaseYear = rYear;
  }

  Mobile.defaultValues() {
    id = 'abcd1111';
    modelName = 'XYZ';
    releaseYear = 2020;
  }
}

void main() {
  Mobile m1 = Mobile('abcd1234', 'XYZ', 2021);
  Mobile m2 = Mobile.defaultValues();

  print(m1.id);
  print(m2.id);
}

Output:

abcd1234
abcd1111

In the previous example, we created another constructor Mobile.defaultValues() which can be used to create an object based on this class like we did to create the object m2.

Constructor Redirects

We can call another constructor from one constructor. Developers often do this to avoid redundancy in the code.

example:

class Mobile {
  String id;
  String modelName;
  int releaseYear;
  Mobile(this.id, this.modelName, this.releaseYear);

  Mobile.withId(uid) : this(uid, 'XYZ', 2022);

  Mobile.defaultValues() : this('abcd4321', 'XYZ', 2022);
}

Mobile m1 = Mobile('abcd1234', 'XYZ', 2021);
Mobile m2 = Mobile.withId('abcd1111');
Mobile m3 = Mobile.defaultValues();

void main() {
  print(m1.id);
  print(m2.id);
  print(m3.id);
}

In this example, instead of writing logic to assign values inside Mobile.withId and Mobile.defaultValues constructor functions, we simply called the main Mobile constructor function with the required values.

Output:

abcd1234
abcd1111
abcd4321

Static Properties

Static properties are the properties of a class which will be available on the class itself but not on the instances of that class. Means, they are not available on the objects created based on that class.

example:

class Mobile {
  String id;
  String modelName;
  int releaseYear;

  static int numberOfMobilesCreated = 0;

  Mobile(this.id, this.modelName, this.releaseYear);

  Mobile.withId(uid) : this(uid, 'XYZ', 2022);

  Mobile.defaultValues() : this('abcd4321', 'XYZ', 2022);
}

void main() {
  var m1 = Mobile('abcd1234', 'XYZ', 2021);
  // m1.numberOfMobilesCreated  // not available
  Mobile.numberOfMobilesCreated++;
  var m2 = Mobile.withId('abcd1111');
  Mobile.numberOfMobilesCreated++;
  var m3 = Mobile.defaultValues();
  Mobile.numberOfMobilesCreated++;

  print(m1.id);
  print(m2.id);
  print(m3.id);
  print(Mobile.numberOfMobilesCreated);
}

In this example, we created a static property numberOfMobilesCreated on the class Mobile. Later you can see that, we can use this property only on Mobile but not on the instances m1 or m2 or m3.

Output:

abcd1234
abcd1111
abcd4321
3

Getters and Setters

Getters & Setters are integrated part of Object Oriented Programming in most of the programming languages. In Dart also they work in exactly the same way.

We can use Getter functions to get any value from an object or provide an indirect access to a private property. Similarly, we can use Setter function to set values to any properties of an object.

example:

class Person {
  String _id;
  String firstName;
  String lastName;

  Person(id, this.firstName, this.lastName) : _id = id;

  String get fullName => firstName + lastName;

  set id(String id) {
    _id = id;
  }
}

void main() {
  var john = Person('abc123', 'John', 'Doe');
  print(john.firstName);
  john.id = 'abc321';
}

In the above class we have a Getter fullDetails and a Setter id. Pay attention to the way used Getter & Setter functions. We use them just like instance properties.

Inheritance

Inheritance is one of the core principle of Object Oriented Programming in which one class inheritis properties and methods from another class.

class Phone {
  String id;
  Phone(this.id);

  void callingFeature() {
    print('Calling...');
  }

  void messagingFeature() {
    print('Messaging...');
  }

  void gamingFeature() {
    print('Gaming...');
  }
}

class FeaturePhone extends Phone {
  FeaturePhone(id) : super(id);
}

class SmartPhone extends Phone {
  SmartPhone(id) : super(id);

  void mailingFeature() {
    print('Mailing...');
  }
}

void main() {
  var f1 = FeaturePhone('f123');
  var s1 = SmartPhone('s123');

  f1.callingFeature();
  s1.mailingFeature();
}

Output:

Calling...
Mailing...

In the above class we have a super class named Phone and two sub classes FeaturePhone and SmartPhone. Both sub-classes inherits all properties and methods from their super class.

While creating the objects, we used the keyword super which refers to their super class.

Overriding

If we want, we can override any property or method which was inherited from the parent class.

example:

class Phone {
  String id;
  Phone(this.id);

  void callingFeature() {
    print('Calling...');
  }

  void messagingFeature() {
    print('Messaging...');
  }

  void gamingFeature() {
    print('Gaming...');
  }
}

class SmartPhone extends Phone {
  SmartPhone(id) : super(id);

  void gamingFeature() {
    print('Overridden Gaming...');
  }

  void mailingFeature() {
    print('Mailing...');
  }
}

void main() {
  var s1 = SmartPhone('s123');

  s1.gamingFeature();
}

Output:

Overridden Gaming...

In this example, we overridden the method gamingFeature inside the sub class. So, you can see the message from the overridden method in console.

Though, we can do like above, it is always advised to use @override annotation for overridden members.

example:

...
@override
  void gamingFeature() {
    print('Overridden Gaming...');
  }
...

Abstract Classes

As the name suggests, Abstract classes are partially implemented classes. That means, we cannot create objects based on Abstract classes. We can use these classes as a super class for sub classes.

We can make the Phone class from the previous example as an Abstract class and let sub classes inherit all properties and methods from it.

abstract class Phone {
  String id;
  Phone(this.id);

  void callingFeature() {
    print('Calling...');
  }

  void messagingFeature() {
    print('Messaging...');
  }

  void gamingFeature() {
    print('Gaming...');
  }
}

class SmartPhone extends Phone {
  SmartPhone(id) : super(id);

  @override
  void gamingFeature() {
    print('Overridden Gaming...');
  }

  void mailingFeature() {
    print('Mailing...');
  }
}

void main() {
  var s1 = SmartPhone('s123');

  s1.gamingFeature();
}

In the previous example we have implemented callingFeature, messagingFeature etc., methods inside the abstract class. Instead of doing this, we can simply mention the methods we want to implement without body and force the sub-classes to implement the body for these methods. If the sub class do not implement these methods then Dart will throw “Missing concrete implementation” error message.

example:

abstract class Phone {
  String id;
  Phone(this.id);

  void callingFeature();

  void messagingFeature();

  void gamingFeature();
}

class SmartPhone extends Phone {
  SmartPhone(id) : super(id);

  @override
  void gamingFeature() {
    print('Overridden Gaming...');
  }

  @override
  void callingFeature() {
    print('Smartphone calling...');
  }

  @override
  void messagingFeature() {
    print('Smartphone messaging');
  }

  void mailingFeature() {
    print('Mailing...');
  }
}

Interfaces

Though many OO Programming languages have interfaces, Dart do not have Interfaces. Instead of interfaces, developers are allowed to use Classes itself work like an interface.

We have seen before that we can extend a class to inherit all of its properties and methods. Instead of extending, we can also implement based on a class.

When you implement a class, then you must implement all the properties and methods which are there in the base class. When we extend a class, if you do not any property/ method of that class, then we automatically inherit them, but it do not happen with implementation.

example:

class Mobile {
  void callingFeature() {
    print('Calling feature from Mobile...');
  }
}

class SmartPhone implements Mobile {
  @override
  void callingFeature() {
    print('Calling feature from SmartPhone...');
  }
}

void main() {
  var s1 = SmartPhone();
  s1.callingFeature();
}

Output:

Calling feature from SmartPhone...

Since we do not have interfaces in Dart, developers usually create abstract classes without method bodies as we have seen earlier and use them similar to an interface.

We can implement more than one class whereas we cannot extend more than one class in Dart.

 

Mixins

We have seen earlier that Dart do not allow multiple inheritance. That means we can inherit from only one class. But, to achieve the functionality, Dart provides Mixins.

A Mixin is similar to a class but without a constructor. We can attach a Mixin to any class to achieve the functionality provided by the mixin.

example:

mixin Gaming {
  void gaming() {
    print('Gaming feature...');
  }
}

class Mobile {
  void calling() {
    print('Calling feature...');
  }
}

class SmartPhone extends Mobile with Gaming {}

void main() {
  var m1 = SmartPhone();
  m1.calling();
  m1.gaming();
}

Output:

Calling feature...
Gaming feature...

In the above example, we created a mixin with a method gaming and a class with a method calling. Then we created a SmartPhone class which inherits from Mobile and also gets the functionality from Gaming mixin.

We can also use an existing class as a mixin provided that the class DO NOT have a constructor. The following example works exactly same as the previous example

class Gaming {
  void gaming() {
    print('Gaming feature...');
  }
}

class Mobile {
  void calling() {
    print('Calling feature...');
  }
}

class SmartPhone extends Mobile with Gaming {}

void main() {
  var m1 = SmartPhone();
  m1.calling();
  m1.gaming();
}

We can also attach more than one mixin to a class.

example:

mixin Gaming {
  void gaming() {
    print('Gaming feature...');
  }
}

mixin Mailing {
  void mailing() {
    print('Mailing feature...');
  }
}

class Mobile {
  void calling() {
    print('Calling feature...');
  }
}

class SmartPhone extends Mobile with Gaming, Mailing {}

void main() {
  var m1 = SmartPhone();
  m1.calling();
  m1.gaming();
  m1.mailing();
}

Leave A Comment

Your email address will not be published. Required fields are marked *