Dart Objects
Last modified June 4, 2025
This Dart objects tutorial explores how to work with objects in the Dart programming language. Objects are the core building blocks of Dart programs, combining data and behavior to create modular, reusable code.
Understanding Objects
In Dart, objects are the fundamental units of a program, encapsulating data and methods. The data, known as instance variables, and the methods, referred to as member functions, are collectively called members of an object. Objects interact with one another through their methods, enabling data processing and communication.
Creating an object involves two steps: defining a class and instantiating it.
A class serves as a blueprint, outlining the state and behavior that all
objects of that class share. Objects, or instances, are created from a class
at runtime using the new
keyword, which is optional in Dart 2.0
and later. We use the dot (.) operator to access an object's instance
variables or methods.
Object-oriented programming (OOP) is a paradigm that leverages objects and their interactions to design robust applications and programs.
Dart Objects
In Dart, everything is an object, including literals like numbers and strings, which distinguishes Dart from many other programming languages.
class Being {} void main() { var b = Being(); print(b.hashCode); print("falcon".toUpperCase()); print(2.isNegative); print(b.runtimeType); print(2.runtimeType); }
This example demonstrates working with various objects in Dart.
class Being {}
The class
keyword defines a template for an object. Here, the
Being
class is empty but still functional.
var b = Being();
A new instance of Being
is created. The new
keyword
is optional in modern Dart.
print(b.hashCode);
Even an empty object inherits methods and attributes from Dart's
Object
class, such as hashCode
.
print("falcon".toUpperCase());
The string literal "falcon"
is an object with methods like
toUpperCase
, showcasing Dart's object-oriented nature.
print(2.isNegative);
Number literals, like 2
, are also objects with properties such
as isNegative
.
print(b.runtimeType); print(2.runtimeType);
The runtimeType
property reveals the type of each object at
runtime.
$ dart main.dart 511903303 FALCON false Being int
Dart Object Attributes
Object attributes, or instance variables, store data specific to each instance of a class. Each object maintains its own copy of these variables. Dart automatically generates getter methods for all instance variables and setter methods for non-final variables.
class Person { String? name; String? occupation; } void main() { var p1 = Person(); p1.name = "John Doe"; p1.occupation = "gardener"; var p2 = Person(); p2.name = "Roger Roe"; p2.occupation = "driver"; print("${p1.name} is a ${p1.occupation}"); print("${p2.name} is a ${p2.occupation}"); }
This example defines a Person
class with two attributes:
name
and occupation
.
var p1 = Person(); p1.name = "John Doe"; p1.occupation = "gardener";
A Person
object is created, and its attributes are set using the
dot operator.
var p2 = Person(); p2.name = "Roger Roe"; p2.occupation = "driver";
A second, unique Person
object is instantiated with different
attribute values.
print("${p1.name} is a ${p1.occupation}"); print("${p2.name} is a ${p2.occupation}");
The attributes of both objects are printed, demonstrating their distinct states.
$ dart main.dart John Doe is a gardener Roger Roe is a driver
Dart Cascade Operator
The cascade operator (..
) enables multiple operations on the same
object in a concise manner, improving code readability.
class User { String? fname; String? lname; String? occupation; @override String toString() => "$fname $lname is a $occupation"; } void main() { var u = User() ..fname = "Roger" ..lname = "Roe" ..occupation = "driver"; print(u); }
This example uses the cascade operator to initialize a User
object's attributes in a single expression.
$ dart main.dart Roger Roe is a driver
Dart Object Methods
Methods are functions defined within a class that operate on an object's attributes, promoting modularity and reusability in code.
import 'dart:math'; class Circle { int radius; Circle(this.radius); double area() => pi * radius * radius; } void main() { var c = Circle(5); print(c.area()); }
This example defines a Circle
class with an area
method to compute the circle's area.
double area() => pi * radius * radius;
The area
method uses the pi
constant from the
dart:math
library to calculate the area.
var c = Circle(5);
A Circle
object is created with a radius of 5.
print(c.area());
The area
method is called using the dot operator to compute and
print the area.
$ dart main.dart 78.53981633974483
Dart Object Constructor
Constructors are special methods automatically invoked when an object is created, used to initialize the object's state. Dart supports named and factory constructors, which do not return values.
Named constructors share the class's name and are commonly used for straightforward initialization.
class User { String name; String occupation; User(this.name, this.occupation); } void main() { var u1 = User("John Doe", "gardener"); var u2 = User("Roger Roe", "driver"); print("${u1.name} is a ${u1.occupation}"); print("${u2.name} is a ${u2.occupation}"); }
This example uses a named constructor to initialize a User
object's attributes.
User(this.name, this.occupation);
The constructor uses automatic initializers to set the name
and
occupation
attributes.
var u1 = User("John Doe", "gardener");
A User
object is created, passing values to the constructor.
$ dart main.dart John Doe is a gardener Roger Roe is a driver
Factory constructors, defined with the factory
keyword, allow
custom logic for object creation, such as caching or conditional
instantiation. They can return existing objects and are useful in design
patterns.
import 'dart:math'; abstract class Shape { factory Shape(String type) { if (type == 'circle') return Circle(4); if (type == 'square') return Square(4); if (type == 'triangle') return Triangle(4); throw Exception("Unknown shape"); } num get area; } class Circle implements Shape { final num radius; Circle(this.radius); num get area => pi * pow(radius, 2); } class Square implements Shape { final num side; Square(this.side); num get area => pow(side, 2); } class Triangle implements Shape { final num side; Triangle(this.side); num get area => pow(side, 2) / 2; } void main() { print(Shape('circle').area); print(Shape('square').area); print(Shape('triangle').area); }
This example uses a factory constructor in an abstract Shape
class to create different shapes based on a type parameter.
$ dart main.dart 50.26548245743669 16 8.0
Dart toString Method
Every Dart object inherits a toString
method from the
Object
class, providing a human-readable string representation.
Calling print
on an object implicitly invokes
toString
.
class User { String name; String occupation; User(this.name, this.occupation); @override String toString() => "$name is a $occupation"; } void main() { var u1 = User("John Doe", "gardener"); var u2 = User("Roger Roe", "driver"); print(u1); print(u2); }
This example overrides the toString
method in the
User
class to provide a custom string representation.
@override String toString() => "$name is a $occupation";
The overridden toString
method returns a formatted string with
the user's name and occupation.
print(u1); print(u2);
Printing the objects automatically calls their toString
method.
$ dart main.dart John Doe is a gardener Roger Roe is a driver
Dart Automatic Initializers
Dart supports concise constructor syntax using automatic initializers, which simplify attribute initialization without explicit assignment statements.
class User { String name; String occupation; User(this.name, this.occupation); } void main() { var u1 = User("John Doe", "gardener"); var u2 = User("Roger Roe", "driver"); print("${u1.name} is a ${u1.occupation}"); print("${u2.name} is a ${u2.occupation}"); }
This example demonstrates automatic initializers in the User
class constructor, inferring data types and reducing boilerplate code.
Dart Named Parameters
Named parameters, denoted by curly braces {}
, allow flexible
constructor calls where parameters are specified by name, enhancing code
clarity.
class User { String? name; String? occupation; User({this.name, this.occupation}); @override String toString() => "$name is a $occupation"; } void main() { var u1 = User(name: "John Doe", occupation: "gardener"); print(u1); }
This example uses named parameters to initialize a User
object,
making the code more readable and flexible.
Dart Object Inheritance
Inheritance allows new classes to build upon existing ones, creating derived
classes that extend or override the functionality of base classes. Dart uses
the extends
keyword for inheritance.
class Being { static int count = 0; Being() { count++; print("Being is created"); } void getCount() => print("There are $count Beings\n"); } class Human extends Being { Human() { print("Human is created"); } } class Animal extends Being { Animal() { print("Animal is created"); } } class Dog extends Animal { Dog() { print("Dog is created"); } } void main() { Human(); var dog = Dog(); dog.getCount(); }
This example demonstrates a class hierarchy where Human
and
Animal
inherit from Being
, and Dog
inherits from Animal
. A static variable tracks instance counts.
static int count = 0;
The static count
variable is shared across all instances of the
Being
class and its subclasses.
Being() { count++; print("Being is created"); }
The Being
constructor increments the count and prints a message
each time an instance is created.
class Animal extends Being { ... class Dog extends Animal { ...
The Animal
and Dog
classes inherit from their
respective parent classes, forming a multi-level hierarchy.
Human(); var dog = Dog(); dog.getCount();
Instances of Human
and Dog
are created, and the
getCount
method displays the total number of Being
instances.
$ dart main.dart Being is created Human is created Being is created Animal is created Dog is created There are 2 Beings
Dart Check Type
Dart's is
keyword checks an object's type at runtime, useful for
type verification in dynamic code.
class Person {} class Student {} void main() { var p = Person(); var s = Student(); print(p is Person); print(s is Person); print(p is Object); print(s is Object); print(2 is int); print(2 is Object); }
This example checks the types of various objects using the is
keyword.
print(p is Person);
This checks if p
is an instance of Person
, which
returns true.
print(s is Person);
This checks if s
is a Person
, which returns false
since s
is a Student
.
print(2 is Object);
Since all Dart objects inherit from Object
, this check returns
true for the number literal 2
.
$ dart main.dart true false true true true true
Dart Getters and Setters
Dart allows explicit definition of getters and setters to control access to an object's attributes, providing encapsulation and validation logic.
class Account { double _balance = 0.0; double get balance => _balance; set balance(double value) { if (value >= 0) { _balance = value; } else { throw Exception("Balance cannot be negative"); } } } void main() { var account = Account(); account.balance = 100.0; print("Balance: ${account.balance}"); try { account.balance = -50.0; } catch (e) { print(e); } }
This example defines an Account
class with a private
_balance
attribute, accessed via getter and setter methods.
double get balance => _balance;
The getter returns the private _balance
value.
set balance(double value) { if (value >= 0) { _balance = value; } else { throw Exception("Balance cannot be negative"); } }
The setter validates that the balance is non-negative before updating.
$ dart main.dart Balance: 100.0 Exception: Balance cannot be negative
Dart Method Overriding
Method overriding allows a subclass to provide a specific implementation of a method defined in its superclass, enabling polymorphic behavior.
class Animal { void makeSound() => print("Some generic sound"); } class Dog extends Animal { @override void makeSound() => print("Woof!"); } class Cat extends Animal { @override void makeSound() => print("Meow!"); } void main() { var dog = Dog(); var cat = Cat(); dog.makeSound(); cat.makeSound(); }
This example demonstrates method overriding in a class hierarchy with
Animal
, Dog
, and Cat
.
@override void makeSound() => print("Woof!");
The Dog
class overrides the makeSound
method to
provide a specific implementation.
$ dart main.dart Woof! Meow!
Dart Mixins
Mixins in Dart allow code reuse by adding functionality to classes without using inheritance, promoting modularity and flexibility.
mixin Flyable { void fly() => print("Flying..."); } class Bird with Flyable { String name; Bird(this.name); } void main() { var eagle = Bird("Eagle"); eagle.fly(); print("${eagle.name} is soaring high!"); }
This example uses a Flyable
mixin to add flying behavior to the
Bird
class.
mixin Flyable { void fly() => print("Flying..."); }
The Flyable
mixin defines a fly
method that can be
shared with any class.
class Bird with Flyable {
The Bird
class incorporates the Flyable
mixin using
the with
keyword.
$ dart main.dart Flying... Eagle is soaring high!
Dart Abstract Classes
Abstract classes in Dart cannot be instantiated and are used to define interfaces or shared functionality for subclasses.
abstract class Vehicle { void startEngine(); } class Car extends Vehicle { @override void startEngine() => print("Car engine started"); } class Bike extends Vehicle { @override void startEngine() => print("Bike engine started"); } void main() { var car = Car(); var bike = Bike(); car.startEngine(); bike.startEngine(); }
This example defines an abstract Vehicle
class with concrete
subclasses Car
and Bike
.
abstract class Vehicle { void startEngine(); }
The abstract Vehicle
class declares a startEngine
method that subclasses must implement.
$ dart main.dart Car engine started Bike engine started
Source
Dart classes - language reference
This tutorial has explored the essentials of working with objects in Dart, including attributes, methods, constructors, inheritance, and more.
Author
Explore all Dart tutorials.