Classes in Dart
By Syncfusion®, Inc. · Last updated
How to design classes with fields and methods, make objects, use this, static members and cascades, and build objects with default, named, redirecting, const and factory constructors.
What Are Classes and Objects?
Class = a blueprint that describes a kind of thing: what it has and what it can do.
Object = one real thing made from that blueprint, with its own values.
For example:
- the class
Studentsays every student has a name and an age, and can introduce themselves - the object Asha, 13 is one student
- the object Ravi, 14 is another student, made from the same class
Simple lesson: a class is the plan, and objects are the things built from the plan.
A Real-World Example
A school uniform shop has one design for the school shirt.
- The design says: a collar, a pocket, a school badge.
- The shop makes many shirts from that one design.
- Each shirt has its own size and its own name tag.
The design is the class. Each shirt is an object.
Every Run box on this page runs on the course's Dart runner in your browser.
It supports classes with fields, methods, constructors and static members. Inheritance comes later, in Inheritance.
What You Will Learn
In this lesson, you will learn:
- What classes and objects are, and how to write them.
- Fields, methods,
this, static members and the cascade operator. - Default and parameterized constructors.
- Named constructors, initializer lists and redirecting constructors.
- Const and factory constructors.
Object Fundamentals
Everything Is an Object
In Dart, every value is an object, even numbers and text.
Root type = Object, the type at the top. Every class is a kind of Object.
Numbers and text are objects too
void main() {
print(5.isEven);
print('Dart'.length);
print(5 is Object);
print('Dart' is Object);
print([1, 2].runtimeType);
}
isEven and length are members of those objects. A dot (.) reaches a member.
Object Members
Every object in Dart has these, because they come from Object:
| Member | What it does |
|---|---|
toString() | gives the object as text. print calls it. |
hashCode | a number used by sets and maps to store the object |
== | checks whether two objects are equal |
runtimeType | the object's type, right now |
noSuchMethod() | runs when a missing member is called on a dynamic value |
You can change how toString() and == behave in your own classes (see below).
Simple lesson: every Dart value is an object, and every object has toString(), hashCode and ==.
Class Fundamentals
Class Structure
class ClassName {
// fields: what each object HAS
// constructor: how to MAKE an object
// methods: what each object can DO
}
Class Declaration
Your first class
class Student {
String name;
int age;
Student(this.name, this.age);
void introduce() {
print('Hi, I am $name and I am $age years old.');
}
}
void main() {
var asha = Student('Asha', 13);
var ravi = Student('Ravi', 14);
asha.introduce();
ravi.introduce();
}
String name;andint age;= fields. Every Student has them.Student(this.name, this.age);= the constructor. It puts the values into the fields (see constructors, below).void introduce()= a method. Every Student can do it.
Class Naming Conventions
- Class names use UpperCamelCase:
Student,ReportCard,LibraryBook. - Use a noun (a thing), in the singular:
Student, notStudents.
Make objects from the class, then click one to call its method:
Simple lesson: a class groups fields and methods under one UpperCamelCase name.
Objects
Creating Objects
Instance = another word for an object made from a class.
- Call the class like a function:
Student('Asha', 13). - In old Dart code you may see
new Student(...). Thenewis optional now.
Accessing Object Members
Use a dot: asha.name, asha.age = 14, asha.introduce().
Reading and changing fields
class Student {
String name;
int age;
Student(this.name, this.age);
}
void main() {
var asha = Student('Asha', 13);
print(asha.name);
print(asha.age);
asha.age = 14;
print('After her birthday: ${asha.age}');
}
Object References
Reference = a variable does not hold the object itself. It holds where the object is.
Two variables can point to the same object:
Two names, one object
identical(a, b) = true only if both point to the very same object.
Passing an object to a function also passes the reference.
If the function changes the object's fields, the caller sees the change too. Make a new object when you need a separate copy.
Simple lesson: a variable holds a reference, so two variables can share one object.
Instance Variables
Instance variable (field) = a variable that belongs to each object. Every object has its own copy.
Type Annotations and Default Values
Fields with types and starting values
class ReportCard {
String studentName;
int maths = 0;
int science = 0;
String? remark;
ReportCard(this.studentName);
}
void main() {
var card = ReportCard('Priya');
print(card.maths);
print(card.remark);
card.maths = 91;
card.remark = 'Excellent';
print('${card.studentName}: ${card.maths}, ${card.remark}');
}
int maths = 0;= every new card starts with 0.String? remark;= may be empty, so it starts asnull(see Null Safety).- A non-nullable field with no value must be set by the constructor, or Dart refuses the class.
Simple lesson: each object has its own fields, and each field needs a starting value.
Methods
Instance method = a function that belongs to a class, and works with that object's fields.
Method Parameters and Return Values
Methods that take inputs and give answers
class ReportCard {
String name;
int maths;
int science;
ReportCard(this.name, this.maths, this.science);
int total() {
return maths + science;
}
bool hasPassed(int passMark) => maths >= passMark && science >= passMark;
void addBonus(int marks) {
maths += marks;
}
}
void main() {
var card = ReportCard('Kiran', 72, 30);
print(card.total());
print(card.hasPassed(35));
card.addBonus(5);
print(card.maths);
}
- Methods use the object's fields directly:
maths + science. - They can take parameters and return values, like any function (see Functions).
Overriding toString()
By default, printing an object shows Instance of 'ClassName'. Give it a better toString():
A better toString
class Student {
String name;
int age;
Student(this.name, this.age);
}
class Teacher {
String name;
String subject;
Teacher(this.name, this.subject);
@override
String toString() => 'Teacher($name, $subject)';
}
void main() {
print(Student('Asha', 13));
print(Teacher('Mrs. Rao', 'Maths'));
}
@override = a note that says "this replaces a member I already had" (here, from Object).
Simple lesson: methods are what an object can do, and toString() decides how it prints.
The this Keyword
this = the current object, the one the method was called on.
Resolving Naming Conflicts
When a parameter has the same name as a field, the parameter wins. Use this. to reach the field:
this. picks the field
class Student {
String name;
int age;
Student(this.name, this.age);
void rename(String name) {
this.name = name;
}
void describe() {
print('${this.name} is $age');
}
}
void main() {
var s = Student('Ashu', 13);
s.rename('Asha');
s.describe();
}
- In
rename,namealone means the parameter.this.namemeans the field. - In
describe, there is no clash, soageandthis.agemean the same. Dart style leavesthis.out when it is not needed.
Simple lesson: use this. only when a parameter hides a field with the same name.
Static Members
Static member = a field or method that belongs to the class itself, not to each object.
Instance or static?
Static Variables and Static Methods
Counting students with a static field
class Student {
static int count = 0;
static const schoolName = 'Green Valley';
String name;
Student(this.name) {
count++;
}
static String welcome() => 'Welcome to $schoolName!';
}
void main() {
print(Student.welcome());
print(Student.count);
Student('Asha');
Student('Ravi');
Student('Priya');
print(Student.count);
}
Accessing Static Members
- Outside the class, write
Student.countandStudent.welcome(). - Inside the class, you can write just
count. - A static method cannot use instance fields like
name, because it does not belong to any one student.
Simple lesson: static members belong to the class and are shared, instance members belong to each object.
The Cascade Operator
Cascade (..) = do several things to the same object, one after another, in one expression.
Setting up an object with cascades
class ReportCard {
String name = '';
int maths = 0;
int science = 0;
void show() {
print('$name: Maths $maths, Science $science');
}
}
void main() {
var card = ReportCard()
..name = 'Meena'
..maths = 88
..science = 93
..show();
print(card.maths + card.science);
}
- Each
..works on the object made byReportCard(). - The whole cascade gives back that object, so
cardgets it. - Method chaining = calling one method after another in a single expression. Cascades let you chain even methods that return nothing.
The Null-Aware Cascade (?..)
?.. = start the cascade only if the object is not null.
?.. skips a null object
class ReportCard {
String name = '';
void show() => print('Card for $name');
}
void main() {
ReportCard? missing;
missing?..name = 'Nobody'..show();
var found = ReportCard()..name = 'Zoya';
found..show();
}
Easy way to remember
- class = the cookie cutter. object = each cookie.
- field = what it has. method = what it does.
- static = one shared copy for the whole class.
- .. = "and also do this, to the same object".
Simple lesson: .. sets up one object step by step, and ?.. does nothing if the object is null.
What Is a Constructor?
Constructor = a special function inside a class that builds a new object and sets up its fields.
- It has the same name as the class.
- It runs once, when the object is made.
- A class can have several constructors, for different ways of building.
For example, a Student can be built:
- from a name and an age
- as a "guest" with default values
- from a line of text like
'Ravi,14'
Simple lesson: a constructor is the recipe Dart follows to build each new object.
A Real-World Example
A school issues ID cards in different ways.
- A new student fills in a form with name and class.
- A visitor gets a ready-made "Guest" card.
- A transfer student brings an old card, and the office copies the details.
Three ways in, but every result is an ID card. Pick a constructor to see what it builds:
Some rules on this page are checked by real Dart before the program runs. Those are shown as plain code.
Default Constructors
Default constructor = the constructor Dart gives you when you write none. It takes no inputs.
No constructor written, but objects can still be made
class Bell {
int rings = 0;
void ring() {
rings++;
print('Ring! ($rings)');
}
}
void main() {
var bell = Bell();
bell.ring();
bell.ring();
}
Every field needs a starting value, because nothing sets it up.
Simple lesson: if you write no constructor, Dart adds an empty one for you.
Parameterized Constructors
Parameterized constructor = a constructor that takes inputs and puts them into fields.
The Long Way
class Student {
late String name;
late int age;
Student(String name, int age) {
this.name = name;
this.age = age;
}
}
The Short Way: this.field
Initializing formal = a parameter written this.name. It puts the argument straight into the field.
this.field does the work for you
class Student {
String name;
int age;
Student(this.name, this.age);
}
void main() {
var s = Student('Asha', 13);
print('${s.name}, ${s.age}');
}
Student(this.name, this.age); does the same as the long way, in one line. This is the Dart style.
Named and Optional Parameters
Constructors can use every parameter style from Functions:
Named, required and default values in a constructor
class Student {
String name;
int age;
String house;
Student({required this.name, required this.age, this.house = 'Not chosen'});
}
void main() {
var a = Student(name: 'Asha', age: 13, house: 'Tagore');
var b = Student(age: 12, name: 'Kiran');
print('${a.name}: ${a.house}');
print('${b.name}: ${b.house}');
}
Simple lesson: this.field parameters fill fields directly, in any parameter style.
Named Constructors
Named constructor = an extra constructor with its own name: ClassName.name(...).
- Use it for a different way of building the same kind of object.
- The name explains the way:
Student.guest(),Student.fromText(...).
A second way to build a Student
class Student {
String name;
int age;
Student(this.name, this.age);
Student.guest()
: name = 'Guest',
age = 0;
@override
String toString() => '$name ($age)';
}
void main() {
print(Student('Asha', 13));
print(Student.guest());
}
Student.guest() sets the fields itself, with an initializer list (the part after :).
Dart has no "overloading": two constructors cannot share a name, even with different inputs.
Named constructors solve this. Each way of building gets its own clear name.
Simple lesson: a named constructor is a second, clearly named recipe for the same class.
Constructor Initializer Lists
Initializer list = the part after : that sets fields before the constructor body runs.
- Items are separated by commas.
- It can use the parameters, but not other methods of the object.
- It is the right place to set
finalfields from a calculation.
An initializer list works out a final field
A final field must get its value before the body runs. In real Dart, setting it inside { } is an error:
class Rectangle {
final int area;
Rectangle(int w, int h) {
area = w * h;
}
}
Dart says Final field 'area' is not initialized. Use an initializer list instead.
Simple lesson: an initializer list sets fields early, which is how final fields get computed values.
Redirecting Constructors
Redirecting constructor = a constructor that just hands the work to another constructor with : this(...).
A newcomer is a Student aged 11
class Student {
String name;
int age;
Student(this.name, this.age);
Student.newcomer(String name) : this(name, 11);
@override
String toString() => '$name ($age)';
}
void main() {
print(Student.newcomer('Kiran'));
print(Student('Zoya', 12));
}
- No body is allowed. The other constructor does everything.
- It avoids writing the same set-up code twice.
Simple lesson: a redirecting constructor passes its inputs on to another constructor.
Constant Constructors
Constant constructor = a const constructor. It lets you make compile-time constant objects (see Variables and Constants).
Rules:
- Every field must be
final. - The call uses
const:const Point(2, 3).
A const constructor
class Point {
final int x;
final int y;
const Point(this.x, this.y);
@override
String toString() => '($x, $y)';
}
void main() {
const start = Point(0, 0);
var finish = Point(4, 3);
print(start);
print(finish);
}
In real Dart, two const Point(0, 0) values are the very same object, so they share memory. This makes Flutter apps faster, because unchanged parts of the screen are built once.
Simple lesson: a const constructor makes objects that are fixed forever, and Dart can share them.
Factory Constructors
Factory constructor = a constructor marked factory. It runs code, then returns an object.
- It can return a new object, or an existing one.
- It is good for building from other data, like text or a map.
Normal constructor or factory?
Building from text with a factory
class Student {
String name;
int age;
Student(this.name, this.age);
factory Student.fromText(String text) {
var parts = text.split(',');
return Student(parts[0].trim(), int.parse(parts[1].trim()));
}
@override
String toString() => '$name ($age)';
}
void main() {
var lines = ['Asha, 13', 'Ravi, 14'];
for (var line in lines) {
print(Student.fromText(line));
}
}
Returning an Existing Object
A factory can keep a cache and hand back the same object again:
One ID card per student, never two
class IdCard {
static final _issued = <String, IdCard>{};
final String holder;
IdCard._(this.holder);
factory IdCard(String holder) {
return _issued.putIfAbsent(holder, () => IdCard._(holder));
}
}
void main() {
var first = IdCard('Asha');
var again = IdCard('Asha');
var other = IdCard('Ravi');
print(identical(first, again));
print(identical(first, other));
}
IdCard._(...)is a private named constructor (see Libraries). Only this file can call it.- The factory gives back the card that was already issued.
You will use factories a lot in File Handling, as fromJson.
Easy way to remember
this.x= fill the field from the argument..name(...)= another way to build.: x = ...= set fields before the body.: this(...)= let another constructor do it.const= fixed forever.factory= run code, then return an object.
Simple lesson: a factory decides which object to return, so it can reuse objects or build them from data.
TL;DR
- A class is a blueprint. An object is one thing made from it:
Student('Asha'). - Fields hold data. Methods do work.
thismeans the current object. - static members belong to the class.
..(cascade) does many things to one object. - Constructors:
Student(this.name);, namedStudent.guest(), initializer lists: x = 1, redirecting: this(...). - const constructors make fixed objects. factory constructors can return an existing or a different object.
Common mistakes
| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
class student | Any case is fine | Class names use UpperCamelCase | It works, but every Dart reader is confused. |
| A non-nullable field with no value | Fields start empty | Set it in the constructor, or give a default | Field must be initialized. |
b = a, then expecting a copy | = copies the object | = copies the reference | Changing b also changes a. |
name = name; in a method | It sets the field | The parameter hides the field | The field never changes. Use this.name. |
asha.count for a static field | Statics belong to objects | Use Student.count | An error. |
A static method using name | Static methods see fields | They belong to no single object | An error. |
Printing an object without toString | It prints its fields | It prints Instance of 'Student' | Unhelpful output. |
| A non-nullable field with no value | It starts empty | Set it in the constructor or give a default | Non-nullable field must be initialized. |
Setting a final field in the body | The body runs first | Use this.x or an initializer list | Final field is not initialized. |
name = name; in a constructor body | It sets the field | The parameter hides the field | Use this.name = name;, or this.name in the parameters. |
| A body on a redirecting constructor | It can add more code | It must only redirect | A compile error. |
A const constructor with a non-final field | const makes fields final | Every field must be final | A compile error. |
Using this in a factory | A factory has an object | No object exists yet | A compile error. |
| Two constructors with the same name | Dart picks by inputs | Every constructor needs its own name | The constructor is already defined. |
Check Yourself
Exercises
What to Remember
- A class is a blueprint, and objects are made from it.
- Fields store data, methods do work, and
thisis the current object. - Static members belong to the class, not to each object.
this.xin a constructor saves a value straight into a field.- Use named, const and factory constructors when one plain constructor is not enough.
Next: Enums. You will learn to describe a fixed set of choices, like the days of the week or the school houses.