Classes

Domains: Dart

Dart is an object-oriented language with classes and mixin-based inheritance. Every object is an instance of a class, and all classes descend from Object. Mixin-based inheritance means that although every class (except for Object) has exactly one superclass, a class body can be reused in multiple class hierarchies.

Using class members

Objects have members consisting of functions and data (methods and instance variables, respectively). When you call a method, you invoke it on an object: the method has access to that object’s functions and data.

Use a dot (.) to refer to an instance variable or method:

var p = Point(2, 2); 

// Set the value of the instance variable y.
p.y = 3; 

// Get the value of y. 
assert(p.y == 3); 

// Invoke distanceTo() on p. 
num distance = p.distanceTo(Point(4, 4)); 

Use ?. instead of . to avoid an exception when the leftmost operand is null:

// If p is non-null, set its y value to 4. 
p?.y = 4;

Using constructors

You can create an object using a constructor. Constructor names can be either ClassName or ClassName.identifier. For example, the following code creates Point objects using the Point() and Point.fromJson() constructors:

var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2}); 

The following code has the same effect, but uses the optional new keyword before the constructor name:

var p1 = new Point(2, 2); var p2 = new Point.fromJson({'x': 1, 'y': 2}); 

Version note: The new keyword became optional in Dart 2.

Some classes provide constant constructors. To create a compile-time constant using a constant constructor, put the const keyword before the constructor name:

var p = const ImmutablePoint(2, 2); 

Constructing two identical compile-time constants results in a single, canonical instance:

var a = const ImmutablePoint(1, 1); 
var b = const ImmutablePoint(1, 1); 
assert(identical(a, b)); // They are the same instance! 

Within a constant context, you can omit the const before a constructor or literal. For example, look at this code, which creates a const map:

// Lots of const keywords here. 
const pointAndLine = const { 
   'point': const [const ImmutablePoint(0, 0)], 
   'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)], 
};

If a constant constructor is outside of a constant context and is invoked without const, it creates a non-constant object:

var a = const ImmutablePoint(1, 1); // Creates a constant 
var b = ImmutablePoint(1, 1); // Does NOT create a constant 

assert(!identical(a, b)); // NOT the same instance! 

Version note: The const keyword became optional within a constant context in Dart 2.

Getting an object’s type

To get an object’s type at runtime, you can use Object’s runtimeType property, which returns a Type object. 

print('The type of a is ${a.runtimeType}'); 

Up to here, you’ve seen how to use classes. The rest of this section shows how to implement classes.

Instance variables

Here’s how you declare instance variables:

class Point { 
  num x; // Declare instance variable x, initially null. 
  num y; // Declare y, initially null. 
  num z = 0; // Declare z, initially 0. 
}

All uninitialized instance variables have the value null. All instance variables generate an implicit getter method. Non-final instance variables also generate an implicit setter method.

class Point { 
   num x; 
   num y; 
} 
void main() { 
   var point = Point(); 
   point.x = 4; // Use the setter method for x. 
   assert(point.x == 4); // Use the getter method for x. 
   assert(point.y == null); // Values default to null. 
} 

If you initialize an instance variable where it is declared (instead of in a constructor or method), the value is set when the instance is created, which is before the constructor and its initializer list execute.

Constructors

Declare a constructor by creating a function with the same name as its class. The most common form of constructor, the generative constructor, creates a new instance of a class:

class Point {
   num x, y; 
   
   Point(num x, num y) { 
   // There's a better way to do this, stay tuned. 
   this.x = x; 
   this.y = y; 
   } 
} 

The this keyword refers to the current instance.

Note: Use this only when there is a name conflict. Otherwise, Dart style omits the this.

The pattern of assigning a constructor argument to an instance variable is so common, Dart has syntactic sugar to make it easy:

class Point { 
   num x, y; 

   // Syntactic sugar for setting x and y before the constructor body runs. 
   Point(this.x, this.y); 
}

Default constructors

If you don’t declare a constructor, a default constructor is provided for you. The default constructor has no arguments and invokes the no-argument constructor in the superclass.

Constructors aren’t inherited

Subclasses don’t inherit constructors from their superclass. A subclass that declares no constructors has only the default (no argument, no name) constructor.

Named constructors

Use a named constructor to implement multiple constructors for a class or to provide extra clarity:

class Point { 
   num x, y; 
   
   Point(this.x, this.y); 

   // Named constructor 
   Point.origin() { 
      x = 0; 
      y = 0; 
   } 
} 

Remember that constructors are not inherited, which means that a superclass’s named constructor is not inherited by a subclass. If you want a subclass to be created with a named constructor defined in the superclass, you must implement that constructor in the subclass.

Invoking a non-default superclass constructor

By default, a constructor in a subclass calls the superclass’s unnamed, no-argument constructor. The superclass’s constructor is called at the beginning of the constructor body. If an initializer list is also being used, it executes before the superclass is called. In summary, the order of execution is as follows:

  1. initializer list
  2. superclass’s no-arg constructor
  3. main class’s no-arg constructor

If the superclass doesn’t have an unnamed, no-argument constructor, then you must manually call one of the constructors in the superclass. Specify the superclass constructor after a colon (:), just before the constructor body (if any).

In the following example, the constructor for the Employee class calls the named constructor for its superclass, Person.

class Person { 
   String firstName; 

   Person.fromJson(Map data) { 
   print('in Person'); 
   } 
} 

class Employee extends Person { 
   // Person does not have a default constructor; you must call super.fromJson(data). 
   Employee.fromJson(Map data) : super.fromJson(data) { 
   print('in Employee'); 
   } 
} 

main() { 
   var emp = new Employee.fromJson({}); 

   // Prints: 
   // in Person 
   // in Employee 
   if (emp is Person) { 
      // Type check 
      emp.firstName = 'Bob'; 
   } 
   (emp as Person).firstName = 'Bob'; 
}

Because the arguments to the superclass constructor are evaluated before invoking the constructor, an argument can be an expression such as a function call:

class Employee extends Person { 
   Employee() : super.fromJson(defaultData); 
   // ··· 
} 

Warning: Arguments to the superclass constructor do not have access to this. For example, arguments can call static methods but not instance methods.

Initializer list

Besides invoking a superclass constructor, you can also initialize instance variables before the constructor body runs. Separate initializers with commas.

// Initializer list sets instance variables before the constructor body runs. 
Point.fromJson(Map<String, num> json) 
   : x = json['x'], 
   y = json['y'] { 
      print('In Point.fromJson(): ($x, $y)'); 
} 

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

During development, you can validate inputs by using assert in the initializer list.

Point.withAssert(this.x, this.y) : assert(x >= 0) { 
   print('In Point.withAssert(): ($x, $y)'); 
} 

Initializer lists are handy when setting up final fields. The following example initializes three final fields in an initializer list.

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); 
}

Redirecting constructors

Sometimes a constructor’s only purpose is to redirect to another constructor in the same class. A redirecting constructor’s body is empty, with the constructor call appearing after a colon (:). 

class Point { 
   num x, y; 

   // The main constructor for this class. 
   Point(this.x, this.y); 

   // Delegates to the main constructor. 
   Point.alongXAxis(num x) : this(x, 0); 
}

Constant constructors

If your class produces objects that never change, you can make these objects compile-time constants. To do this, define a const constructor and make sure that all instance variables are final.

class ImmutablePoint { 
   static final ImmutablePoint origin = 
      const ImmutablePoint(0, 0); 
   final num x, y; 
   const ImmutablePoint(this.x, this.y); 
} 

Constant constructors don’t always create constants. 

Factory constructors

Use the factory keyword when implementing a constructor that doesn’t always create a new instance of its class. For example, a factory constructor might return an instance from a cache, or it might return an instance of a subtype.

The following example demonstrates a factory constructor returning objects from a cache:

class Logger { 
   final String name; 
   bool mute = false; 

   // _cache is library-private, thanks to the _ in front of its name. 
   static final Map<String, Logger> _cache = 
      <String, Logger>{}; 

   factory Logger(String name) { 
      return _cache.putIfAbsent( 
         name, () => Logger._internal(name)); 
   } 

   Logger._internal(this.name); 
   
   void log(String msg) { 
      if (!mute) print(msg); 
   } 
} 

Note: Factory constructors have no access to this.

Invoke a factory constructor just like you would any other constructor:

var logger = Logger('UI'); 
logger.log('Button clicked');

Methods

Methods are functions that provide behavior for an object.

Instance methods

Instance methods on objects can access instance variables and this. The distanceTo() method in the following sample is an example of an instance method:

import 'dart:math'; 
class Point { 
   num x, y; 

   Point(this.x, this.y); 

   num distanceTo(Point other) { 
      var dx = x - other.x; 
      var dy = y - other.y; 
      return sqrt(dx * dx + dy * dy); 
   } 
}

Getters and setters

Getters and setters are special methods that provide read and write access to an object’s properties. Recall that each instance variable has an implicit getter, plus a setter if appropriate. You can create additional properties by implementing getters and setters, using the get and set keywords:

class Rectangle { 
   num left, top, width, height; 
   Rectangle(this.left, this.top, this.width, this.height); 

   // Define two calculated properties: right and bottom. 
   num get right => left + width; 
   set right(num value) => left = value - width; 
   num get bottom => top + height; 
   set bottom(num value) => top = value - height; 
} 

void main() { 
   var rect = Rectangle(3, 4, 20, 15); 
   assert(rect.left == 3); 
   rect.right = 12; 
   assert(rect.left == -8); 
} 

With getters and setters, you can start with instance variables, later wrapping them with methods, all without changing client code.

Note: Operators such as increment (++) work in the expected way, whether or not a getter is explicitly defined. To avoid any unexpected side effects, the operator calls the getter exactly once, saving its value in a temporary variable.

Abstract methods

Instance, getter, and setter methods can be abstract, defining an interface but leaving its implementation up to other classes. Abstract methods can only exist in abstract classes.

To make a method abstract, use a semicolon (;) instead of a method body:

abstract class Doer { 
   // Define instance variables and methods... 

   void doSomething(); // Define an abstract method. 
} 

class EffectiveDoer extends Doer { 
   void doSomething() { 
      // Provide an implementation, so the method is not abstract here... 
   } 
}

Abstract classes

Use the abstract modifier to define an abstract class — a class that can’t be instantiated. Abstract classes are useful for defining interfaces, often with some implementation. If you want your abstract class to appear to be instantiable, define a factory constructor.

Abstract classes often have abstract methods. Here’s an example of declaring an abstract class that has an abstract method:

// This class is declared abstract and thus can't be instantiated. 
abstract class AbstractContainer { 
   // Define constructors, fields, methods... 

   void updateChildren(); // Abstract method. 
}

Implicit interfaces

Every class implicitly defines an interface containing all the instance members of the class and of any interfaces it implements. If you want to create a class A that supports class B’s API without inheriting B’s implementation, class A should implement the B interface.

A class implements one or more interfaces by declaring them in an implements clause and then providing the APIs required by the interfaces. For example:

// A person. The implicit interface contains greet(). 
class Person { 
   // In the interface, but visible only in this library. 
   final _name; 

   // Not in the interface, since this is a constructor. 
   Person(this._name); 

   // In the interface. 
   String greet(String who) => 'Hello, $who. I am $_name.'; 
} 

// An implementation of the Person interface. 
class Impostor implements Person { 
   get _name => ''; 
   String greet(String who) => 'Hi $who. Do you know who I am?'; 
} 

String greetBob(Person person) => person.greet('Bob'); 

void main() { 
   print(greetBob(Person('Kathy'))); 
   print(greetBob(Impostor())); 
} 

Here’s an example of specifying that a class implements multiple interfaces:

class Point implements Comparable, Location {...}

Extending a class

Use extends to create a subclass, and super to refer to the superclass:

class Television { 
   void turnOn() { 
      _illuminateDisplay(); 
      _activateIrSensor(); 
   } 
   // ··· 
} 

class SmartTelevision extends Television { 
   void turnOn() { 
      super.turnOn(); 
      _bootNetworkInterface(); 
      _initializeMemory(); 
      _upgradeApps(); 
   } 
   // ··· 
}

Overriding members

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

class SmartTelevision extends Television { 
   @override void turnOn() {...} 
   // ··· 
} 

To narrow the type of a method parameter or instance variable in code that is type safe, you can use the covariant keyword.

Overridable operators

You can override the operators shown in the following table. For example, if you define a Vector class, you might define a + method to add two vectors.

<
>
<=
>=
-
+
/
~/
*
%
|
^
&
<<
>>
[]
[]=
~
==
 
 
 
 
 

Note: You may have noticed that != is not an overridable operator. The expression e1 != e2 is just syntactic sugar for !(e1 == e2).

Here’s an example 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); 

   // Operator == and hashCode not shown. 
} 

void main() { 
   final v = Vector(2, 3); 
   final w = Vector(2, 2); 
   assert(v + w == Vector(4, 5)); 
   assert(v - w == Vector(0, 1)); 
} 

If you override ==, you should also override Object’s hashCode getter

noSuchMethod()

To detect or react whenever code attempts to use a non-existent method or instance variable, you can override noSuchMethod():

class A { 
   // Unless you override noSuchMethod, using a non-existent member results in a NoSuchMethodError. 
   @override 
   void noSuchMethod(Invocation invocation) { 
      print('You tried to use a non-existent member: ' + '${invocation.memberName}'); 
   } 
} 

You can’t invoke an unimplemented method unless one of the following is true:

  • The receiver has the static type dynamic.
  • The receiver has a static type that defines the unimplemented method (abstract is OK), and the dynamic type of the receiver has an implemention of noSuchMethod() that’s different from the one in class Object.

Enumerated types

Enumerated types, often called enumerations or enums, are a special kind of class used to represent a fixed number of constant values.

Using enums

Declare an enumerated type using the enum keyword:

enum Color { red, green, blue } 

Each value in an enum has an index getter, which returns the zero-based position of the value in the enum declaration. For example, the first value has index 0, and the second value has index 1.

assert(Color.red.index == 0); 
assert(Color.green.index == 1); 
assert(Color.blue.index == 2); 

To get a list of all of the values in the enum, use the enum’s values constant.

List<Color> colors = Color.values;
assert(colors[2] == Color.blue); 

You can use enums in switch statements, and you’ll get a warning if you don’t handle all of the enum’s values:

var aColor = Color.blue; 

switch (aColor) { 
   case Color.red: 
      print('Red as roses!'); 
      break; 
   case Color.green: 
      print('Green as grass!'); 
      break; 
   default: // Without this, you see a WARNING. 
      print(aColor); // 'Color.blue' 
} 

Enumerated types have the following limits:

  • You can’t subclass, mix in, or implement an enum.
  • You can’t explicitly instantiate an enum.

Adding features to a class: mixins

Mixins are a way of reusing a class’s code in multiple class hierarchies.

To use a mixin, use the with keyword followed by one or more mixin names. The following example shows two classes that use mixins:

class Musician extends Performer with Musical { 
   // ··· 
}

class Maestro extends Person with Musical, Aggressive, Demented { 
   Maestro(String maestroName) { 
      name = maestroName; 
      canConduct = true; 
   } 
} 

To implement a mixin, create a class that extends Object and declares no constructors. Unless you want your mixin to be usable as a regular class, use the mixin keyword instead of class. For example:

mixin Musical { 
   bool canPlayPiano = false; 
   bool canCompose = false; 
   bool canConduct = false; 
   
   void entertainMe() { 
      if (canPlayPiano) { 
         print('Playing piano'); 
      } 
      else if (canConduct) { 
         print('Waving hands'); 
      } else { 
         print('Humming to self'); 
      } 
   } 
}

To specify that only certain types can use the mixin — for example, so your mixin can invoke a method that it doesn’t define — use on to specify the required superclass:

mixin MusicalPerformer on Musician { 
   // ··· 
}

Version note: Support for the mixin keyword was introduced in Dart 2.1. Code in earlier releases usually used abstract class instead.

Class variables and methods

Use the static keyword to implement class-wide variables and methods.

Static variables

Static variables (class variables) are useful for class-wide state and constants:

class Queue { 
   static const initialCapacity = 16; 
   // ··· 
} 

void main() { 
   assert(Queue.initialCapacity == 16); 
}

Static variables aren’t initialized until they’re used.

Static methods

Static methods (class methods) do not operate on an instance, and thus do not have access to this. For example:

class Point { 
   num x, y; 
   Point(this.x, this.y); 

   static num distanceBetween(Point a, Point b) { 
      var dx = a.x - b.x; 
      var dy = a.y - b.y; 
      return sqrt(dx * dx + dy * dy); 
   } 
} 

void main() { 
   var a = Point(2, 2); 
   var b = Point(4, 4); 
   var distance = Point.distanceBetween(a, b); 
   assert(2.8 < distance && distance < 2.9); 
   print(distance); 
}

Note: Consider using top-level functions, instead of static methods, for common or widely used utilities and functionality.

You can use static methods as compile-time constants. For example, you can pass a static method as a parameter to a constant constructor.

Similar pages

Page structure
Terms

Constructor

Methods

Variables

Getters and setters

Classes

main()

Dart

List

Functions

Initializer list

Factory constructor

Set

Named constructor

Constant constructor

Instance methods

Types

String

Map

Default constructors

noSuchMethod()

bool

int