February 5, 2025 · 3 min read
OOP in Dart: Classes and Objects
Classes and objects are the foundation of everything in Dart. Build the concept from the ground up — fields, constructors, getters, static members, and const.
Object-oriented programming is the backbone of Dart. Everything — even an int — is an object, and understanding classes and objects is the prerequisite for everything else: state management, models, architecture. Let's build the concept from the ground up.
Classes and objects
A class is a blueprint. An object (or instance) is a concrete thing built from that blueprint. The class Dog describes what every dog has and can do; myDog is one specific dog.
class Dog {
String name = '';
int age = 0;
void bark() => print('$name says woof!');
}
void main() {
final myDog = Dog(); // create an instance
myDog.name = 'Rex';
myDog.age = 3;
myDog.bark(); // Rex says woof!
}
Constructors
Setting fields one by one is tedious. A constructor builds an object in a valid state. Dart's shorthand assigns parameters straight to fields.
class Dog {
Dog(this.name, this.age); // shorthand: assigns to fields
final String name;
final int age;
void bark() => print('$name says woof!');
}
final rex = Dog('Rex', 3);
Named parameters and defaults
Named parameters make call sites self-documenting. Mark them required when they must be provided.
class User {
User({required this.email, this.isAdmin = false});
final String email;
final bool isAdmin;
}
final u = User(email: 'a@b.com');
final admin = User(email: 'root@b.com', isAdmin: true);
Named constructors
A class can offer multiple ways to be built.
class Point {
Point(this.x, this.y);
Point.origin() : x = 0, y = 0; // named constructor
Point.fromMap(Map<String, num> m) // build from data
: x = m['x']!.toDouble(),
y = m['y']!.toDouble();
final double x;
final double y;
}
final a = Point(3, 4);
final b = Point.origin();
Fields, getters, and setters
Beyond plain fields, you can expose computed values with getters and validate writes with setters.
class Circle {
Circle(this.radius);
double radius;
double get area => 3.14159 * radius * radius; // computed, read-only
set diameter(double d) => radius = d / 2; // validated write
}
final c = Circle(2);
print(c.area); // 12.566...
c.diameter = 10; // sets radius to 5
Instance vs. static members
Instance members belong to each object. Static members belong to the class itself — shared across all instances.
class Counter {
static int instances = 0; // shared
Counter() { instances++; }
static Counter create() => Counter(); // factory-style helper
}
Counter();
Counter();
print(Counter.instances); // 2
Useful overrides: toString
Override toString so your objects print meaningfully — invaluable when debugging.
class Money {
const Money(this.amount, this.currency);
final double amount;
final String currency;
@override
String toString() => '$amount $currency';
}
print(Money(9.99, 'USD')); // 9.99 USD
const constructors
If every field is final and known at compile time, a const constructor lets Dart canonicalize identical instances — the same object is reused, which is a real performance win in Flutter.
class Color {
const Color(this.hex);
final int hex;
}
const red = Color(0xFF0000);
const alsoRed = Color(0xFF0000);
print(identical(red, alsoRed)); // true — same instance
The takeaway
A class bundles data (fields) with behavior (methods) and guarantees a valid initial state through its constructor. Master this and you're ready for the four pillars of OOP — inheritance, encapsulation, abstraction, and polymorphism — which are all just ways of relating classes to one another.