Fluttering Dart
Fluttering Dart: OOP
Classes, Objects, Interfaces, and a lot more

Flutter projects can use both platform-specific and cross-platform code. The latter is written in Dart, and, for building Flutter apps, some basic knowledge of Dart is required.
Fluttering Dart’s goal is to explore fundamental knowledge and unveil tips & tricks of the powerful programming language that brings Flutter to life.
In the previous parts of the series, we went through the Dart built-in data types, functions, operators and control flow statements.
In this part, we’ll discover Dart as the true object-orientated programming language it is.
Some of the code examples can be tried out, and played with, using DartPad.
Classes and Objects
Remember the built-in data types we’ve covered at the beginning of our journey? Classes allow us to define our very own data types! This way we can model objects we need to use in our programs.
A class is a user-defined data type and in the examples, up to this point we’ve already defined some classes, the most memorable probably being the Cat class.
In Dart, every object is an instance of a class and all classes descend from Object. Dart also has a mixin-based inheritance and that comes to aid the lack of multiple inheritances. This inheritance type allows the reuse of multiple class bodies and the existence of exactly one superclass.
Classes define members: functions and data (methods and instance variables). Invoking a method on an object is the act of calling a method. A public method has access to that object’s members.
The dot operator . is used to refer to a variable or method.
We can create an object of a defined class using one of its constructors.
Constructor names can be either the class name ClassName or ClassName.identifier. For example we’ll create a Cat using Cat() or Cat.copyCat() constructors.
Constructors can have arguments to provide necessary values to initialize new objects and are of several types:
- default — when we don’t declare a constructor, a default constructor that has no arguments and invokes the no-argument constructor in the superclass is provided; note that if you declare a constructor there will be no default constructor and if you extend a class its constructor is not inherited;
class Cat {
DateTime birthday; // default
// it's here
// even if
// you can't see it
}- named — used when we need to implement multiple constructors for a class or to provide extra clarity;
class Cat {
DateTime birthday; // named
Cat.baby() {
birthday = DateTime.now();
}
}- redirecting — when we just want to redirect certain constructor in the same class; its body is empty with the constructor call appearing after
:;
class Cat {
DateTime birthday;
// main cosntructor
Cat(this.birthday); // delegating to main constructor
Cat.withBirthday(DateTime birthday) : this(birthday);
}- constant — use when we need objects that never change; when calling such constructors we should use the keyword
constotherwise we won’t create constants;
class CatTreat {
static final CatTreat catTreat = const CatTreat(1);
final num quantity; // constant
const CatTreat(this.quantity);
}- factory — when implementing a constructor that doesn’t always create a new instance of its class; a factory constructor might return a cached instance, do object pooling, or it might return an instance of a subtype; factory constructors don’t have access to
this.
import 'dart:math';class Cat extends Pet {
DateTime birthday;
Cat(this.birthday);
// delegating to main constructor
Cat.withBirthday(DateTime birthday) : this(birthday);
}
class Dog extends Pet {
DateTime birthday;
Dog(this.birthday);
// delegating to main constructor
Dog.withBirthday(DateTime birthday) : this(birthday);
}
// factory
class Pet {
Pet();
factory Pet.withBirthday(DateTime birthday) {
bool isCat = Random.secure().nextBool();
return isCat?Cats(birthday):Dog(birthday);
}
}The Pet factory constructor from above returns a random Cat or a Dog (subclasses of Pet).
Callable classes
Dart classes can also behave like functions (they can be invoked, take arguments and return something).
To enable this, we have to define the call() method inside the class.
class Cat {
DateTime birthday; Cat(this.birthday); String call() {
print('Meow!');
}
}
void main() {
var cat = Cat(DateTime.now());
cat();
// prints
// Meow!
}Generators
Generators are used when we need to lazily produce a sequence of values. Dart supports two types of generator functions:
- a synchronous generator that returns an Iterable object
- and an asynchronous generator that returns a Stream object
To implement the synchronous generator we mark the function body as sync*, and use the yield statement to return values:
Iterable<Cat> kittens(int toSpawn) sync* {
int kittenIndex = 0;
while(kittenIndex < n) {
kittenIndex++;
yield Cat.baby();
}
}To implement an asynchronous generator we mark the function body as async*, and use the yield statement to return values:
Stream<Cat> kittens(int toSpawn) async* {
int kittenIndex = 0;
while(kittenIndex < n) {
kittenIndex++;
yield Cat.baby();
}
}If we use recursive calls, a performance improvement can be achieved by using yield*:
Iterable<Cat> kittens(int toSpawn) sync* {
if(toSpawn > 0) {
yield Cat.baby;
yield* kittens(toSpawn - 1);
}
}Variables
There are two flavors: instance and class variables.
All uninitialized variables have by default the value null. Also, all of the variables that are not final will generate an implicit getter and setter. The final ones, will not generate a setter.
By default, variables are instance variables. If initialized when declared (instead of in a constructor or method), their value is set when the instance is created, which is before the constructor and its initializer list execute.
To create a class variable we’ll use the static keyword. These are useful for class-wide state and constants. They are not initialized until they’re used.
Methods
Methods are functions that provide behavior for an object.
Like in the case of variables, here are also two flavors: instance and class methods.
Instance methods on objects can access instance variables and this.
Static methods (class methods) do not operate on an instance, and thus do not have access to this. They are best used as compile-time constants (for example, passed as a parameter to a constant constructor). We should use top-level functions, instead of static methods, for common or widely used utilities and functionality.
Encapsulation
Dart doesn’t contain keywords for restricting access, like public, protected or private used in Java. The encapsulation happens at library level, not at class level.
There is a simple rule: any identifier (class, class member, top-level function, or variable) that starts with an underscore _ it is private to its library.
Inheritance and composition
Inheritance allows extending a class to a specialized version of that class. As said before all classes inherit from the Object type, just by declaring a class, we extend the Object type. Dart allows single direct inheritance and has special support for mixins, which can be used to extend class functionalities without direct inheritance, simulating multiple inheritances, and reusing code. This is how composition is achieved.
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. 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.
There’s no final class, so a class can always be extended.
Abstraction
Abstraction is the process through which we define a class and its essential characteristics, leaving implementation for its subclasses.
To declare an abstract class, we use the abstract keyword. These classes can’t be instantiated and are useful for defining interfaces. Abstract classes can have abstract methods.
There is no interface keyword. The way it works is that every declared class defines an implicit interface containing all instance members of a class and of any interfaces it implements. This means that any class can be implemented by others without extending it.
A class can implement one or more interfaces by using the implements keyword.
Polymorphism
Polymorphism is achieved through inheritance and represents the ability of an object to copy the behavior of another (the int or double are also a num).
We can use the extends to create a subclass and super to refer to the superclass.
Subclasses usually override instance methods, getters, and setters. We can use the @override annotation to indicate that we’re overriding a member.
Dart doesn’t allow overloading. To overcome this we can use the flexible argument definitions (optional and positional).
Overall, Dart provides all of the bells and whistles that we need to use the OOP paradigm.
In the next part of the Fluttering Dart series, we’ll delve into Futures and Isolates to discover how to overcome Dart’s single-thread downside.
Tha(nk|t’)s all!






