The object-oriented programming is a way to organize code around classes and objects.
We’ve reached one of the fundamental pillars. Dart is an Object-Oriented language.
What does this mean in practice? Almost everything you touch in Dart is an object. And when we make the leap to Flutter, you’ll see that every visual component (a button, a text, a list) is a Class.
If you master classes in Dart, understanding how a Flutter interface is built will be a piece of cake. If you don’t, it will seem like a maze of parentheses. Let’s clarify it.
Classes and objects
The concept is the classic one in programming:
- Class: It’s the blueprint, the template, or the mold. It defines what characteristics the object will have.
- Object: It’s the real instance created from that mold. It’s the house built using the blueprint.
Basic definition
To define a class we use the reserved word class. By convention, class names use PascalCase (first letter uppercase).
class Robot {
// Properties (Attributes)
String name;
double battery;
// Constructor (explained below)
Robot(this.name, this.battery);
// Methods (Functions inside the class)
void greet() {
print("Hello, I'm $name and I have $battery% battery.");
}
}To use it (instantiate it), simply call the constructor. Note that in modern Dart the new keyword is optional and is usually not used.
void main() {
var r2d2 = Robot("R2D2", 100.0); // Create the object
r2d2.greet();
}Constructors: the Dart way
This is where Dart differentiates itself from Java or C# and becomes much more efficient.
The verbosity problem
In other languages, a typical constructor looks like this (a lot of repetition):
// Explicit form, valid but repetitive
class Robot {
String name;
Robot(String name) : name = name;
}The solution: simplified constructors
Dart allows us to inject the parameter value directly into the property using this..
// ✅ Dart way (Syntactic Sugar)
class Robot {
String name;
// Directly assigns the argument to the property
Robot(this.name);
}Constructors with named parameters
The named parameters from the previous article are used extensively in classes. In fact, almost all Flutter widgets use this type of constructor.
class Button {
String text;
String color;
// Constructor with named parameters
// We use 'required' because the properties cannot be null
Button({required this.text, this.color = "Blue"});
}
void main() {
// Look how readable!
var myButton = Button(text: "Accept", color: "Green");
}Named constructors
Dart does not support “method overloading” (having two functions with the same name but different parameters) in the traditional way.
To solve this and allow creating objects in different ways, Named Constructors exist.
The syntax is ClassName.constructorName().
class Robot {
String name;
double battery;
// Main constructor
Robot(this.name, this.battery);
// Named constructor: "origin"
// Creates a basic robot with a name and 100% battery
Robot.origin(this.name) : battery = 100.0;
// Named constructor: "unknown"
Robot.unknown()
: name = "Unknown",
battery = 0;
}
void main() {
var robot1 = Robot("C3PO", 50);
var robot2 = Robot.origin("BB8"); // Uses the named constructor
}The most famous constructor you’ll use is .fromJson(). It is used to create objects from data received from the internet (APIs). We’ll see it in the data connection module.
Encapsulation: private properties
In Dart, there are no public, private, or protected keywords.
Visibility is controlled at the library level (generally, at the file level).
To make a property or method private, simply start its name with an underscore _.
class BankAccount {
// Public property
String holder;
// Private property (only accessible within this file)
double _balance;
BankAccount(this.holder, this._balance);
// Public method to access in a controlled way
void viewBalance() {
print("The balance is $_balance");
}
}If you try to access _balance from another file (main.dart), Dart will tell you it doesn’t exist.
Getters and setters
If we have a private property, how do we read or modify it from outside? Using get and set.
class Square {
double _side;
Square(this._side);
// Getter: Allows reading a calculated or private property
double get area => _side * _side;
// Setter: Allows validating data when assigning
set side(double value) {
if (value < 0) throw 'The side cannot be negative';
_side = value;
}
}
void main() {
var s = Square(5);
print(s.area); // 25 (Used like a property, without parentheses)
s.side = 10; // Use the setter
// s.side = -5; // Would throw our exception
}