On this page27 sections
- Prerequisites
- Connection With Previous Lectures
- Learning Objectives
- 1. Declaring a Class
- 2. Creating an Object
- 3. Reading and Changing Fields
- 4. Printing a Whole Object
- 5. Every Object Has Its Own Fields
- 6. A Field Must Have a Value
- 7. Nullable Fields
- 8. late Fields
- 9. A Field Calculated From Other Fields
- 10. Lazy Initialization
- 11. Methods
- 12. Two Variables, One Object
- 13. Setting Fields With Cascades
- 14. Objects in a List
- 15. A Real-World Example
- Code Examples
- Common Beginner Mistakes
- Best Practices
- Mini Challenge
- Practice Questions
- Practice Answers
- Quick Revision
- Key Takeaways
- What's Next?
Lecture 53 — Creating Classes & Objects
| Category | 9 — Object-Oriented Programming |
| Lecture | 053 of 140 |
| Estimated video length | 11 min |
| Target | Dart 3.12.2 |
| Level | Beginner |
A map can hold a student's name and marks, but nothing stops a typo in a key. A class gives a record its own type, with named fields Dart checks for you and methods that work on them.
Contents
Prerequisites · Connection · Objectives · Lesson · Code Examples · Common Mistakes · Best Practices · Mini Challenge · Practice Questions · Answers · Takeaways
Every error message in this lecture is real. Each one was produced by running the code shown on Dart 3.12.2, so you will recognize it when you meet it yourself.
Prerequisites
Students should already understand, from Lectures 001–052:
finalandlatevariables (Lecture 006)- nullable types and
??,?.(Lectures 012–014) - the cascade operator
..(Lecture 020) - functions, parameters and return values (Lectures 030–033)
- lists and
for-inloops (Lectures 039–041) - the ideas of objects, state and behaviour (Lecture 052)
Connection With Previous Lectures
In Lecture 051 we stored a student in a map:
var ali = {'name': 'Ali', 'marks': 80};Dart cannot tell that ali['nmae'] is a typo — it just returns null. Every value comes back
nullable, and the code that works on a student lives somewhere else. Lecture 052 showed the
cure — an object with its own data and behaviour — and a first tiny class. Now we look
closely at how classes and objects really work.
Learning Objectives
By the end of this lecture you will be able to:
- Declare a class with fields that have initial values
- Create objects from a class, without the
newkeyword - Read and change fields with the dot
. - Use nullable and
latefields, including a lazylatefield - Write methods that read and change an object's fields
- Explain why two objects are independent, and why two variables can share one object
- Store objects in lists and set their fields with cascades
.. - Recognize the errors Dart gives for common class mistakes
1. Declaring a Class
A class is the blueprint from Lecture 052: it describes what every object of one kind has. The variables inside it are called fields.
class Student {
String name = '';
int age = 0;
}Class names use UpperCamelCase (Student, BankAccount). A class is written outside
main(), at the top level of the file.
2. Creating an Object
Write the class name followed by () to create a new object — an instance of the class:
void main() {
var ali = Student();
ali.name = 'Ali';
ali.age = 20;
print(ali.name);
print(ali.age);
}Ali
20This is what StringBuffer() did in Lecture 020. Older code writes new Student(); new is
optional and modern style omits it.
3. Reading and Changing Fields
A dot . reaches into the object: student.marks reads a field, and student.marks = 85
changes it. Fields start with the initial values written in the class.
class Student {
String name = 'Unknown';
int marks = 0;
}
void main() {
var student = Student();
print('${student.name}: ${student.marks}');
student.marks = 85;
student.marks = student.marks + 5;
print('${student.name}: ${student.marks}');
}Unknown: 0
Unknown: 90Inside a string, a field needs braces: ${student.marks}, not $student.marks.
4. Printing a Whole Object
Printing the object itself does not show its fields:
class Student {
String name = 'Ali';
}
void main() {
var ali = Student();
print(ali);
print('Student: $ali');
}Instance of 'Student'
Student: Instance of 'Student'Teaching a class how to print itself, by overriding
toString(), comes in Lecture 060 — Method Overriding. Until then, print the fields.
5. Every Object Has Its Own Fields
One class can make as many objects as you like, and each keeps its own values:
class Student {
String name = '';
int marks = 0;
}
void main() {
var ali = Student();
var sara = Student();
ali.name = 'Ali';
ali.marks = 80;
sara.name = 'Sara';
sara.marks = 92;
print('${ali.name}: ${ali.marks}');
print('${sara.name}: ${sara.marks}');
}Ali: 80
Sara: 92Changing sara.marks never touches ali.marks.
6. A Field Must Have a Value
A non-nullable field without an initial value is the same error Lecture 004 showed for top-level variables:
class Student {
String name; // ✗ no value
}Error: Field 'name' should be initialized because its type 'String' doesn't allow null.
String name;
^^^^For now, give each field an initial value. Lecture 054 — Constructors shows how to fill fields with real values at the moment an object is created.
7. Nullable Fields
When "no value yet" is a real possibility, make the field nullable. It starts as null, and
all the null-safety tools from Category 3 work on it:
class Student {
String name = 'Sara';
String? email;
}
void main() {
var sara = Student();
print(sara.email);
print(sara.email ?? 'no email');
print(sara.email?.length);
sara.email = 'sara@school.com';
print(sara.email);
}null
no email
null
sara@school.com8. late Fields
A late field promises a value before anyone reads it, exactly like the late variables of
Lecture 006:
class Account {
late String owner;
int balance = 0;
}
void main() {
var account = Account();
account.owner = 'Zayan';
print('${account.owner}: ${account.balance}');
}Zayan: 0Break the promise and the program stops while running:
void main() {
var account = Account();
print(account.owner); // ✗ never assigned
}Unhandled exception:
LateInitializationError: Field 'owner' has not been initialized.9. A Field Calculated From Other Fields
A normal field's initial value cannot use other fields:
class Item {
int price = 500;
int quantity = 2;
int total = price * quantity; // ✗
}Error: Can't access 'this' in a field initializer to read 'quantity'.
int total = price * quantity;
^^^^^^^^
Error: Can't access 'this' in a field initializer to read 'price'.
int total = price * quantity;
^^^^^Fields are created before the object is complete, so they cannot read each other yet. (this
means "the current object"; we study it in Lecture 056.) Adding late fixes it — the
calculation now waits until total is first read:
class Item {
int price = 500;
int quantity = 2;
late int total = price * quantity;
}
void main() {
var item = Item();
item.quantity = 3;
print(item.total);
}150010. Lazy Initialization
This is the second use of late promised in Lecture 006: a late field with an initial value
is calculated on first read, and only once.
String makeCode() {
print('Making the code...');
return 'STU-104';
}
class Student {
late String code = makeCode();
}
void main() {
var ali = Student();
print('Student created');
print(ali.code);
print(ali.code);
}Student created
Making the code...
STU-104
STU-104makeCode() ran at the first ali.code, not at creation; the second read reused the stored
result. Useful when a value is expensive and may never be needed.
11. Methods
A function written inside a class is a method. It can read and change the object's fields directly by name:
class Account {
String owner = '';
int balance = 0;
void deposit(int amount) {
balance = balance + amount;
}
bool canPay(int price) {
return balance >= price;
}
void describe() {
print('$owner has $balance');
}
}
void main() {
var account = Account();
account.owner = 'Ali';
account.deposit(500);
account.deposit(250);
account.describe();
print(account.canPay(1000));
}Ali has 750
falseaccount.deposit(500) means "run deposit on this account". Data and the code that works on
it now live together.
12. Two Variables, One Object
A variable does not contain the object — it refers to it. Copying the variable copies the reference, not the object:
class Student {
String name = '';
}
void main() {
var first = Student();
first.name = 'Ali';
var second = first;
second.name = 'Sara';
print(first.name);
print(second.name);
}Sara
SaraOnly one Student() was created, so there is one object. That explains equality too:
class Student {
String name = 'Ali';
}
void main() {
var a = Student();
var b = Student();
var c = a;
print(a == b);
print(a == c);
print(identical(a, c));
}false
true
truea and b hold the same data but are two objects, so == says false. c is the very same
object as a. Teaching your own classes what == should mean comes in Lecture 060 — Method
Overriding.
13. Setting Fields With Cascades
Lecture 020 promised cascades would set properties. Because a cascade gives back the object, you can create it and fill every field in one statement:
class Student {
String name = '';
int age = 0;
int marks = 0;
}
void main() {
var zayan = Student()
..name = 'Zayan'
..age = 6
..marks = 95;
print('${zayan.name}, ${zayan.age}, ${zayan.marks}');
}Zayan, 6, 9514. Objects in a List
Objects go into lists like any other value, and a loop can work through them:
class Student {
String name = '';
int marks = 0;
}
void main() {
var students = [
Student()
..name = 'Ali'
..marks = 80,
Student()
..name = 'Sara'
..marks = 92,
];
var total = 0;
for (var student in students) {
print('${student.name}: ${student.marks}');
total = total + student.marks;
}
print('Average: ${total / students.length}');
}Ali: 80
Sara: 92
Average: 86.0Dart infers students as List<Student>, so student.marks is known to be an int.
15. A Real-World Example
A shopping cart: two classes, nullable and list fields, methods, and cascades.
class CartItem {
String product = '';
int price = 0;
int quantity = 1;
int total() {
return price * quantity;
}
}
class Cart {
String owner = '';
String? coupon;
List<CartItem> items = [];
void add(CartItem item) {
items.add(item);
}
void printReceipt() {
var subtotal = 0;
print('Cart of $owner');
for (var item in items) {
print('${item.product} x ${item.quantity}: ${item.total()}');
subtotal = subtotal + item.total();
}
print('Subtotal: $subtotal');
print('Coupon: ${coupon ?? 'none'}');
}
}
void main() {
var notebook = CartItem()
..product = 'Notebook'
..price = 500
..quantity = 2;
var pen = CartItem()
..product = 'Pen'
..price = 50
..quantity = 4;
var cart = Cart()
..owner = 'Sara'
..add(notebook)
..add(pen);
cart.printReceipt();
}Cart of Sara
Notebook x 2: 1000
Pen x 4: 200
Subtotal: 1200
Coupon: noneCode Examples
Example 1 — Default Values, Then Changes
class Book {
String title = 'Untitled';
int pages = 0;
}
void main() {
var book = Book();
print('${book.title}, ${book.pages} pages');
book.title = 'Dart Basics';
book.pages = 240;
print('${book.title}, ${book.pages} pages');
}Untitled, 0 pages
Dart Basics, 240 pagesExample 2 — Independent Counters
class Counter {
int count = 0;
void increment() {
count++;
}
}
void main() {
var a = Counter();
var b = Counter();
a.increment();
a.increment();
b.increment();
print('a: ${a.count}');
print('b: ${b.count}');
}a: 2
b: 1Example 3 — A Nullable Field With a Fallback
class Person {
String name = 'Zayan';
String? nickname;
}
void main() {
var person = Person();
print(person.nickname ?? person.name);
person.nickname = 'Zee';
print(person.nickname ?? person.name);
}Zayan
ZeeExample 4 — A Method That Returns a Value
class Rectangle {
double width = 0;
double height = 0;
double area() {
return width * height;
}
}
void main() {
var room = Rectangle()
..width = 4
..height = 3.5;
print('Area: ${room.area()}');
}Area: 14.0Example 5 — Printing the Object vs Its Fields
class Pet {
String name = '';
String kind = '';
}
void main() {
var pet = Pet()
..name = 'Milo'
..kind = 'cat';
print(pet);
print('${pet.name} is a ${pet.kind}');
}Instance of 'Pet'
Milo is a catExample 6 — late Fields
class Order {
late String customer;
int quantity = 1;
int price = 200;
late int total = price * quantity;
}
void main() {
var order = Order()
..customer = 'Ali'
..quantity = 4;
print('${order.customer} pays ${order.total}');
}Ali pays 800Example 7 — Student Marks
class Student {
String name = '';
List<int> marks = [];
double average() {
var sum = 0;
for (var mark in marks) {
sum = sum + mark;
}
return sum / marks.length;
}
bool hasPassed() {
return average() >= 50;
}
}
void main() {
var sara = Student()
..name = 'Sara'
..marks = [80, 75, 85];
print('${sara.name}: ${sara.average()}');
print('Passed: ${sara.hasPassed()}');
}Sara: 80.0
Passed: trueA method can call another method of the same object: hasPassed() uses average().
Example 8 — Shopping Total
class Product {
String name = '';
int price = 0;
}
void main() {
var products = [
Product()
..name = 'Notebook'
..price = 500,
Product()
..name = 'Pen'
..price = 50,
Product()
..name = 'Bag'
..price = 1500,
];
var total = 0;
for (var product in products) {
print('${product.name}: ${product.price}');
total = total + product.price;
}
print('Total: $total');
}Notebook: 500
Pen: 50
Bag: 1500
Total: 2050Example 9 — Bank Account
class BankAccount {
String owner = '';
int balance = 0;
void deposit(int amount) {
balance = balance + amount;
}
void withdraw(int amount) {
if (amount > balance) {
print('Not enough money for $amount');
return;
}
balance = balance - amount;
}
}
void main() {
var account = BankAccount()..owner = 'Zayan';
account.deposit(1000);
account.withdraw(250);
account.withdraw(2000);
print('${account.owner}: ${account.balance}');
}Not enough money for 2000
Zayan: 750The guard clause in withdraw protects the balance. Code outside can still write
account.balance = -5, though — Lecture 057 — Private Members & Encapsulation closes that
door.
Example 10 — Temperature
class Thermometer {
String city = '';
double celsius = 0;
double fahrenheit() {
return celsius * 9 / 5 + 32;
}
}
void main() {
var lahore = Thermometer()
..city = 'Lahore'
..celsius = 35;
var murree = Thermometer()
..city = 'Murree'
..celsius = 12;
for (var t in [lahore, murree]) {
print('${t.city}: ${t.celsius} C = ${t.fahrenheit()} F');
}
}Lahore: 35.0 C = 95.0 F
Murree: 12.0 C = 53.6 FCommon Beginner Mistakes
1. Forgetting the parentheses
// var ali = Student; // ✗ the class itself, not an object
// ali.name = 'Ali';Error: The setter 'name' isn't defined for the type 'Type'.Student names the class; Student() creates an object. (A setter is anything you can
assign to; Lecture 056 explains the word.)
2. Misspelling a field
// ali.nmae = 'Ali'; // ✗Error: The setter 'nmae' isn't defined for the type 'Student'.This is the typo a map could not catch — a class catches it before the program runs.
3. Using the class name instead of an object
// Student.name = 'Ali'; // ✗Error: Setter not found: 'name'.Fields belong to objects: write ali.name.
4. Leaving a non-nullable field without a value
// class Student {
// String name; // ✗
// }Error: Field 'name' should be initialized because its type 'String' doesn't allow null.5. Using a nullable field as if it were not
// print(sara.email.length); // ✗ email is String?Error: Property 'length' cannot be accessed on 'String?' because it is potentially null.
Try accessing using ?. instead.6. Assigning the wrong type to a field
// ali.marks = '85'; // ✗ marks is intError: A value of type 'String' can't be assigned to a variable of type 'int'.7. Expecting var b = a; to copy the object
Both variables refer to one object (section 12).
Best Practices
- Name classes in UpperCamelCase and fields and methods in lowerCamelCase.
- Give each field a sensible initial value, or make it nullable when "no value" is meaningful.
- Use
lateonly when the value truly arrives later, or for a lazy calculation. - Prefer a method over a
latefield for values that must stay up to date. - Use cascades to create and fill an object in one readable statement.
Mini Challenge
Write a Player class with name and score (starting at 0) and a method
addPoints(int points). Create Ali and Sara with cascades. Ali scores 10 then 20; Sara
scores 15. Print both scores and the winner.
Ali: 30
Sara: 15
Winner: AliTry it yourself before opening the solution.
Show solution
class Player {
String name = '';
int score = 0;
void addPoints(int points) {
score = score + points;
}
}
void main() {
var ali = Player()..name = 'Ali';
var sara = Player()..name = 'Sara';
ali.addPoints(10);
sara.addPoints(15);
ali.addPoints(20);
print('${ali.name}: ${ali.score}');
print('${sara.name}: ${sara.score}');
var winner = ali.score > sara.score ? ali : sara;
print('Winner: ${winner.name}');
}Ali: 30
Sara: 15
Winner: AliPractice Questions
Beginner
- Declare a class
Citywithname = 'Lahore'andpopulation = 0. Create an object and print its name. - Declare a class
Bookwithtitle = ''andpages = 0. Create a book, set the title to'Dart'and pages to120, and printDart: 120 pages. - A class
Phonehasbrand = 'Nokia'. What doprint(phone)andprint(phone.brand)print?
Application
-
A class
Studenthasname = 'Sara'and a nullableemail. Print the email, orno emailif there is none. -
Write a class
Circlewithradius = 0and a methodarea()returning3.14 * radius * radius. Set the radius to2with a cascade and print the area. -
Write a class
Walletwithownerandmoney. Create one with a cascade for Ali with300, and printAli has 300. -
Predict the output, then check it:
var a = Box()..label = 'A'; var b = a; b.label = 'B'; print(a.label);
Challenge
- Store Ali (
80), Sara (92) and Zayan (67) asStudentobjects in a list. Find and printTop student: Sara (92)with a loop. - A class
Itemhasprice = 500,quantity = 2andlate int total = price * quantity;. Printtotal, setquantityto5, then printtotalagain. Explain the output. - Write a class
Librarywithname, aList<String> books, and methodsaddBook,hasBook(returnsbool) andprintBooks. Create'City Library'and add two books in one cascade, then print the books and check for'Clean Code'and'Flutter'.
Practice Answers
Question 1 — Solution
class City {
String name = 'Lahore';
int population = 0;
}
void main() {
var city = City();
print(city.name);
}LahoreQuestion 2 — Solution
class Book {
String title = '';
int pages = 0;
}
void main() {
var book = Book();
book.title = 'Dart';
book.pages = 120;
print('${book.title}: ${book.pages} pages');
}Dart: 120 pagesQuestion 3 — Solution
class Phone {
String brand = 'Nokia';
}
void main() {
var phone = Phone();
print(phone);
print(phone.brand);
}Instance of 'Phone'
NokiaQuestion 4 — Solution
class Student {
String name = 'Sara';
String? email;
}
void main() {
var sara = Student();
print(sara.email ?? 'no email');
}no emailQuestion 5 — Solution
class Circle {
double radius = 0;
double area() {
return 3.14 * radius * radius;
}
}
void main() {
var circle = Circle()..radius = 2;
print(circle.area());
}12.56Question 6 — Solution
class Wallet {
String owner = '';
int money = 0;
}
void main() {
var wallet = Wallet()
..owner = 'Ali'
..money = 300;
print('${wallet.owner} has ${wallet.money}');
}Ali has 300Question 7 — Solution
class Box {
String label = '';
}
void main() {
var a = Box()..label = 'A';
var b = a;
b.label = 'B';
print(a.label);
}Ba and b refer to the same box.
Question 8 — Solution
class Student {
String name = '';
int marks = 0;
}
void main() {
var students = [
Student()
..name = 'Ali'
..marks = 80,
Student()
..name = 'Sara'
..marks = 92,
Student()
..name = 'Zayan'
..marks = 67,
];
var best = students[0];
for (var student in students) {
if (student.marks > best.marks) {
best = student;
}
}
print('Top student: ${best.name} (${best.marks})');
}Top student: Sara (92)Question 9 — Solution
class Item {
int price = 500;
int quantity = 2;
late int total = price * quantity;
}
void main() {
var item = Item();
print(item.total);
item.quantity = 5;
print(item.total);
}1000
1000A lazy late field is calculated once, at its first read, and then stored. Changing
quantity afterwards does not recalculate it — a total() method would.
Question 10 — Solution
class Library {
String name = '';
List<String> books = [];
void addBook(String title) {
books.add(title);
}
bool hasBook(String title) {
return books.contains(title);
}
void printBooks() {
print('$name has ${books.length} books:');
for (var book in books) {
print('- $book');
}
}
}
void main() {
var library = Library()
..name = 'City Library'
..addBook('Dart Basics')
..addBook('Clean Code');
library.printBooks();
print(library.hasBook('Clean Code'));
print(library.hasBook('Flutter'));
}City Library has 2 books:
- Dart Basics
- Clean Code
true
falseQuick Revision
- What is the difference between a class and an object?
- How do you create an object in Dart? Is
newneeded? - What does
print(object)show for a class you wrote? - Why must a
Stringfield have an initial value? - When is a lazy
latefield calculated? - What is a method?
- After
var b = a;, how many objects are there? - Why does
a == bgivefalsefor two objects with the same data?
Key Takeaways
- A class declares fields and methods; an object is one instance created with
ClassName()— nonewneeded. - The dot
.reads fields, changes them, and calls methods. - Each object keeps its own field values.
- Non-nullable fields need a value; nullable fields start as
null;latefields promise a value later. - A
latefield with an initializer is lazy: calculated on first read, once, and it may use other fields. - Variables refer to objects. Copying a variable shares the object, and
==on two separate objects isfalseby default. - Objects work in lists and loops, and cascades create and fill them in one statement.
- Printing an object shows
Instance of 'ClassName'untiltoString()is overridden.
What's Next?
Lecture 54 — Constructors. Every object in this lecture started with placeholder values
like '' and 0 and was filled in afterwards. Constructors let you give an object its real
values at the moment it is created — so a Student can never exist without a name.