Watch the video (11 min)
On this page27 sections
  1. Prerequisites
  2. Connection With Previous Lectures
  3. Learning Objectives
  4. 1. Declaring a Class
  5. 2. Creating an Object
  6. 3. Reading and Changing Fields
  7. 4. Printing a Whole Object
  8. 5. Every Object Has Its Own Fields
  9. 6. A Field Must Have a Value
  10. 7. Nullable Fields
  11. 8. late Fields
  12. 9. A Field Calculated From Other Fields
  13. 10. Lazy Initialization
  14. 11. Methods
  15. 12. Two Variables, One Object
  16. 13. Setting Fields With Cascades
  17. 14. Objects in a List
  18. 15. A Real-World Example
  19. Code Examples
  20. Common Beginner Mistakes
  21. Best Practices
  22. Mini Challenge
  23. Practice Questions
  24. Practice Answers
  25. Quick Revision
  26. Key Takeaways
  27. What's Next?

Lecture 53 — Creating Classes & Objects

Category9 — Object-Oriented Programming
Lecture053 of 140
Estimated video length11 min
TargetDart 3.12.2
LevelBeginner

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:

  • final and late variables (Lecture 006)
  • nullable types and ??, ?. (Lectures 012–014)
  • the cascade operator .. (Lecture 020)
  • functions, parameters and return values (Lectures 030–033)
  • lists and for-in loops (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:

dart
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 new keyword
  • Read and change fields with the dot .
  • Use nullable and late fields, including a lazy late field
  • 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.

dart
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:

dart
void main() {
  var ali = Student();
  ali.name = 'Ali';
  ali.age = 20;
  print(ali.name);
  print(ali.age);
}
output
Ali
20

This 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.

dart
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}');
}
output
Unknown: 0
Unknown: 90

Inside 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:

dart
class Student {
  String name = 'Ali';
}

void main() {
  var ali = Student();
  print(ali);
  print('Student: $ali');
}
output
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:

dart
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}');
}
output
Ali: 80
Sara: 92

Changing 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:

dart
class Student {
  String name; // ✗ no value
}
output
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:

dart
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);
}
output
null
no email
null
sara@school.com

8. late Fields

A late field promises a value before anyone reads it, exactly like the late variables of Lecture 006:

dart
class Account {
  late String owner;
  int balance = 0;
}

void main() {
  var account = Account();
  account.owner = 'Zayan';
  print('${account.owner}: ${account.balance}');
}
output
Zayan: 0

Break the promise and the program stops while running:

dart
void main() {
  var account = Account();
  print(account.owner); // ✗ never assigned
}
output
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:

dart
class Item {
  int price = 500;
  int quantity = 2;
  int total = price * quantity; // ✗
}
output
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:

dart
class Item {
  int price = 500;
  int quantity = 2;
  late int total = price * quantity;
}

void main() {
  var item = Item();
  item.quantity = 3;
  print(item.total);
}
output
1500

10. 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.

dart
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);
}
output
Student created
Making the code...
STU-104
STU-104

makeCode() 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:

dart
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));
}
output
Ali has 750
false

account.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:

dart
class Student {
  String name = '';
}

void main() {
  var first = Student();
  first.name = 'Ali';
  var second = first;
  second.name = 'Sara';
  print(first.name);
  print(second.name);
}
output
Sara
Sara

Only one Student() was created, so there is one object. That explains equality too:

dart
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));
}
output
false
true
true

a 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:

dart
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}');
}
output
Zayan, 6, 95

14. Objects in a List

Objects go into lists like any other value, and a loop can work through them:

dart
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}');
}
output
Ali: 80
Sara: 92
Average: 86.0

Dart 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.

dart
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();
}
output
Cart of Sara
Notebook x 2: 1000
Pen x 4: 200
Subtotal: 1200
Coupon: none

Code Examples

Example 1 — Default Values, Then Changes

dart
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');
}
output
Untitled, 0 pages
Dart Basics, 240 pages

Example 2 — Independent Counters

dart
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}');
}
output
a: 2
b: 1

Example 3 — A Nullable Field With a Fallback

dart
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);
}
output
Zayan
Zee

Example 4 — A Method That Returns a Value

dart
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()}');
}
output
Area: 14.0

Example 5 — Printing the Object vs Its Fields

dart
class Pet {
  String name = '';
  String kind = '';
}

void main() {
  var pet = Pet()
    ..name = 'Milo'
    ..kind = 'cat';
  print(pet);
  print('${pet.name} is a ${pet.kind}');
}
output
Instance of 'Pet'
Milo is a cat

Example 6 — late Fields

dart
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}');
}
output
Ali pays 800

Example 7 — Student Marks

dart
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()}');
}
output
Sara: 80.0
Passed: true

A method can call another method of the same object: hasPassed() uses average().

Example 8 — Shopping Total

dart
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');
}
output
Notebook: 500
Pen: 50
Bag: 1500
Total: 2050

Example 9 — Bank Account

dart
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}');
}
output
Not enough money for 2000
Zayan: 750

The 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

dart
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');
  }
}
output
Lahore: 35.0 C = 95.0 F
Murree: 12.0 C = 53.6 F

Common Beginner Mistakes

1. Forgetting the parentheses

dart
// var ali = Student; // ✗ the class itself, not an object
// ali.name = 'Ali';
output
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

dart
// ali.nmae = 'Ali'; // ✗
output
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

dart
// Student.name = 'Ali'; // ✗
output
Error: Setter not found: 'name'.

Fields belong to objects: write ali.name.

4. Leaving a non-nullable field without a value

dart
// class Student {
//   String name; // ✗
// }
output
Error: Field 'name' should be initialized because its type 'String' doesn't allow null.

5. Using a nullable field as if it were not

dart
// print(sara.email.length); // ✗ email is String?
output
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

dart
// ali.marks = '85'; // ✗ marks is int
output
Error: 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 late only when the value truly arrives later, or for a lazy calculation.
  • Prefer a method over a late field 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.

output
Ali: 30
Sara: 15
Winner: Ali

Try it yourself before opening the solution.

Show solution
dart
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}');
}
output
Ali: 30
Sara: 15
Winner: Ali

Practice Questions

Beginner

  1. Declare a class City with name = 'Lahore' and population = 0. Create an object and print its name.
  2. Declare a class Book with title = '' and pages = 0. Create a book, set the title to 'Dart' and pages to 120, and print Dart: 120 pages.
  3. A class Phone has brand = 'Nokia'. What do print(phone) and print(phone.brand) print?

Application

  1. A class Student has name = 'Sara' and a nullable email. Print the email, or no email if there is none.

  2. Write a class Circle with radius = 0 and a method area() returning 3.14 * radius * radius. Set the radius to 2 with a cascade and print the area.

  3. Write a class Wallet with owner and money. Create one with a cascade for Ali with 300, and print Ali has 300.

  4. Predict the output, then check it:

    dart
    var a = Box()..label = 'A';
    var b = a;
    b.label = 'B';
    print(a.label);

Challenge

  1. Store Ali (80), Sara (92) and Zayan (67) as Student objects in a list. Find and print Top student: Sara (92) with a loop.
  2. A class Item has price = 500, quantity = 2 and late int total = price * quantity;. Print total, set quantity to 5, then print total again. Explain the output.
  3. Write a class Library with name, a List<String> books, and methods addBook, hasBook (returns bool) and printBooks. 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

dart
class City {
  String name = 'Lahore';
  int population = 0;
}

void main() {
  var city = City();
  print(city.name);
}
output
Lahore

Question 2 — Solution

dart
class Book {
  String title = '';
  int pages = 0;
}

void main() {
  var book = Book();
  book.title = 'Dart';
  book.pages = 120;
  print('${book.title}: ${book.pages} pages');
}
output
Dart: 120 pages

Question 3 — Solution

dart
class Phone {
  String brand = 'Nokia';
}

void main() {
  var phone = Phone();
  print(phone);
  print(phone.brand);
}
output
Instance of 'Phone'
Nokia

Question 4 — Solution

dart
class Student {
  String name = 'Sara';
  String? email;
}

void main() {
  var sara = Student();
  print(sara.email ?? 'no email');
}
output
no email

Question 5 — Solution

dart
class Circle {
  double radius = 0;

  double area() {
    return 3.14 * radius * radius;
  }
}

void main() {
  var circle = Circle()..radius = 2;
  print(circle.area());
}
output
12.56

Question 6 — Solution

dart
class Wallet {
  String owner = '';
  int money = 0;
}

void main() {
  var wallet = Wallet()
    ..owner = 'Ali'
    ..money = 300;
  print('${wallet.owner} has ${wallet.money}');
}
output
Ali has 300

Question 7 — Solution

dart
class Box {
  String label = '';
}

void main() {
  var a = Box()..label = 'A';
  var b = a;
  b.label = 'B';
  print(a.label);
}
output
B

a and b refer to the same box.

Question 8 — Solution

dart
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})');
}
output
Top student: Sara (92)

Question 9 — Solution

dart
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);
}
output
1000
1000

A 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

dart
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'));
}
output
City Library has 2 books:
- Dart Basics
- Clean Code
true
false

Quick Revision

  1. What is the difference between a class and an object?
  2. How do you create an object in Dart? Is new needed?
  3. What does print(object) show for a class you wrote?
  4. Why must a String field have an initial value?
  5. When is a lazy late field calculated?
  6. What is a method?
  7. After var b = a;, how many objects are there?
  8. Why does a == b give false for two objects with the same data?

Key Takeaways

  • A class declares fields and methods; an object is one instance created with ClassName() — no new needed.
  • 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; late fields promise a value later.
  • A late field 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 is false by default.
  • Objects work in lists and loops, and cascades create and fill them in one statement.
  • Printing an object shows Instance of 'ClassName' until toString() 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.


Video lecture

Subscribe
Placeholder video — the real recording isn't linked yet.