Watch the video (12 min)
On this page23 sections
  1. Prerequisites
  2. Connection With Previous Lectures
  3. Learning Objectives
  4. 1. What this Means
  5. 2. Usually You Can Leave this Out
  6. 3. When this Is Required
  7. 4. Returning this for Chaining
  8. 5. Computed Getters
  9. 6. A Getter with a Block Body
  10. 7. Setters
  11. 8. A Setter That Checks the Value
  12. 9. Read-Only Properties
  13. 10. Field, Getter, or Method?
  14. 11. A Real-World Example
  15. Code Examples
  16. Common Beginner Mistakes
  17. Best Practices
  18. Mini Challenge
  19. Practice Questions
  20. Practice Answers
  21. Quick Revision
  22. Key Takeaways
  23. What's Next?

Lecture 56 — this, Getters & Setters

Category9 — Object-Oriented Programming
Lecture056 of 140
Estimated video length12 min
TargetDart 3.12.2
LevelBeginner

So far a field was a plain variable that anyone could overwrite with anything. Getters and setters let a property calculate its value or check a new one — and this lets an object refer to itself.

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 052–055:

  • classes, objects, fields, and methods
  • constructors, including the Student(this.name) shorthand
  • final fields (Lecture 006) and arrow functions => (Lecture 034)
  • shadowing (Lecture 037) and the cascade operator .. (Lecture 020)

Connection With Previous Lectures

Here is a class built the way Lectures 053–055 taught:

dart
class Student {
  String name;
  int marks;

  Student(this.name, this.marks);
}

void main() {
  var ali = Student('Ali', 80);
  ali.marks = -40;
  print('${ali.name}: ${ali.marks}');
}
output
Ali: -40

Nothing stopped a mark of -40. A field accepts any value of its type, and the class has no chance to say "that makes no sense". And some values — a grade, an area, a total — should not be stored at all, but worked out from other fields every time you ask. This lecture gives a class both powers.


Learning Objectives

By the end of this lecture you will be able to:

  • Explain what this refers to inside a class
  • Recognize when this is required and when it is unnecessary
  • Return this from a method to chain calls, and compare that with cascades
  • Write computed getters with get
  • Write setters with set, including setters that validate a value
  • Combine a getter and a setter into one property
  • Create read-only properties with a getter only or a final field
  • Recognize the errors Dart gives for common getter and setter mistakes

1. What this Means

Inside a class, this means "the object this code is running on".

Analogy — when Ali says "my name", he means Ali's name; when Sara says the same words, she means hers. this is the object's way of saying "my".

dart
class Student {
  String name;

  Student(this.name);

  void introduce() {
    print('Hi, I am ${this.name}');
  }
}

void main() {
  Student('Ali').introduce();
  Student('Sara').introduce();
}
output
Hi, I am Ali
Hi, I am Sara

The same method prints two different names, because this is a different object each time.


2. Usually You Can Leave this Out

Inside a method, a plain name already means the object's field, so ${this.name} and ${name} do the same thing. In fact, dart analyze flags the version above:

output
info - Unnecessary 'this.' qualifier. Try removing 'this.'. - unnecessary_this

Dart's style is to write this only when it is needed. Write print('Hi, I am $name');.


3. When this Is Required

this becomes necessary when a parameter has the same name as a field. The parameter shadows the field, exactly like the shadowing in Lecture 037:

dart
class Student {
  String name;

  Student(this.name);

  void rename(String name) {
    name = name;
  }
}

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

No error, no warning — and the name did not change. Inside rename, both names mean the parameter, so the line copies the parameter into itself. this.name reaches past the parameter to the field:

dart
  void rename(String name) {
    this.name = name;
  }
output
Sara

This is also what Student(this.name) means in a constructor: "put this argument into the field called name".


4. Returning this for Chaining

A method can return this — the object itself — so the next call continues on it:

dart
class Counter {
  int count = 0;

  Counter add(int amount) {
    count += amount;
    return this;
  }
}

void main() {
  var counter = Counter().add(5).add(3).add(2);
  print(counter.count);
}
output
10

You already know a tool that does this without changing the class: the cascade. With a plain void add(int amount) method, the cascade still hands back the object:

dart
class Counter {
  int count = 0;

  void add(int amount) {
    count += amount;
  }
}

void main() {
  var counter = Counter()
    ..add(5)
    ..add(3)
    ..add(2);
  print(counter.count);
}
output
10

Prefer cascades. They work on every class, and the method's return value stays free for a real result.


5. Computed Getters

A getter is a property whose value is calculated each time you read it. Write the type, the keyword get, the name — and no parentheses:

dart
class Rectangle {
  double width;
  double height;

  Rectangle(this.width, this.height);

  double get area => width * height;
}

void main() {
  var room = Rectangle(4, 3);
  print(room.area);
  room.width = 5;
  print(room.area);
}
output
12.0
15.0

You read room.area like a field, with no (). Because nothing is stored, the area can never go out of date when the width changes.


6. A Getter with a Block Body

A getter that needs more than one expression uses { } and return, like any function:

dart
class Student {
  String name;
  int marks;

  Student(this.name, this.marks);

  String get grade {
    if (marks >= 80) return 'A';
    if (marks >= 60) return 'B';
    return 'C';
  }
}

void main() {
  var sara = Student('Sara', 85);
  print('${sara.name}: ${sara.grade}');
}
output
Sara: A

7. Setters

A setter runs code when you assign to a property. Write set, the name, and exactly one parameter. A getter and a setter with the same name form one property:

dart
class Thermometer {
  double celsius;

  Thermometer(this.celsius);

  double get fahrenheit => celsius * 9 / 5 + 32;

  set fahrenheit(double value) {
    celsius = (value - 32) * 5 / 9;
  }
}

void main() {
  var t = Thermometer(25);
  print(t.fahrenheit);
  t.fahrenheit = 212;
  print(t.celsius);
}
output
77.0
100.0

t.fahrenheit = 212 looks like a normal assignment, but it calls the setter, which updates celsius. Only one value is stored; fahrenheit is a view of it from another angle.


8. A Setter That Checks the Value

Now we can fix the -40 problem. The setter decides whether to accept a value. It needs a field to store the accepted value in, and that field needs a different name from the property:

dart
class Student {
  String name;
  int storedMarks;

  Student(this.name, this.storedMarks);

  int get marks => storedMarks;

  set marks(int value) {
    if (value < 0 || value > 100) {
      print('Rejected: $value is not between 0 and 100');
      return;
    }
    storedMarks = value;
  }
}

void main() {
  var ali = Student('Ali', 80);
  ali.marks = -40;
  ali.marks = 95;
  print('${ali.name}: ${ali.marks}');
}
output
Rejected: -40 is not between 0 and 100
Ali: 95

The code outside the class still writes ali.marks = ..., but now the class gets a say.

There is a gap, though: nothing stops someone from writing ali.storedMarks = -40 and skipping the setter entirely. Closing that gap with private members is the topic of Lecture 057.


9. Read-Only Properties

A property with a getter and no setter can be read but not assigned:

dart
class Rectangle {
  final double width;
  final double height;

  Rectangle(this.width, this.height);

  double get area => width * height;
}

void main() {
  var room = Rectangle(4, 3);
  room.area = 20;
}
output
Error: The setter 'area' isn't defined for the type 'Rectangle'.
 - 'Rectangle' is from 'bin/main.dart'.
Try correcting the name to the name of an existing setter, or defining a setter or field named 'area'.
  room.area = 20;
       ^^^^

A final field is read-only too — writing room.width = 5; gives the same message for 'width'. A final field is set once by the constructor; a getter-only property is calculated. Both are safe to share, because no one can overwrite them.


10. Field, Getter, or Method?

  • Field — a value the object stores: name, width.
  • Getter — a quick value worked out from fields, with no inputs: area, grade.
  • Method — an action, or anything that needs arguments: rename('Sara'), add(5).

If you would describe it with a noun and it needs no arguments, it is usually a getter.


11. A Real-World Example

dart
class BankAccount {
  final String owner;
  double storedBalance = 0;

  BankAccount(this.owner);

  double get balance => storedBalance;

  set balance(double value) {
    if (value < 0) {
      print('Rejected: balance cannot go below 0');
      return;
    }
    storedBalance = value;
  }

  bool get isEmpty => storedBalance == 0;

  void deposit(double amount) {
    balance = balance + amount;
  }

  void withdraw(double amount) {
    balance = balance - amount;
  }
}

void main() {
  var account = BankAccount('Sara')
    ..deposit(1000)
    ..withdraw(300)
    ..withdraw(900);
  print('${account.owner}: ${account.balance}');
  print('Empty: ${account.isEmpty}');
}
output
Rejected: balance cannot go below 0
Sara: 700.0
Empty: false

owner is a read-only final field, isEmpty a computed getter, and balance a checked getter/setter pair. Because deposit and withdraw also go through the setter, the rule is written once and applies everywhere. The cascade calls three methods on the new account.


Code Examples

Example 1 — this Inside a Method

dart
class Pet {
  String name;

  Pet(this.name);

  void greet() {
    print('$name says hello');
  }
}

void main() {
  Pet('Zayan\'s cat').greet();
}
output
Zayan's cat says hello

Example 2 — this with a Shadowing Parameter

dart
class City {
  String name;

  City(this.name);

  void update(String name) {
    this.name = name;
  }
}

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

Example 3 — Returning this

dart
class Score {
  int points = 0;

  Score bonus(int amount) {
    points += amount;
    return this;
  }
}

void main() {
  print(Score().bonus(10).bonus(15).points);
}
output
25

Example 4 — A Cascade on Your Own Class

dart
class Score {
  int points = 0;

  void bonus(int amount) {
    points += amount;
  }
}

void main() {
  var score = Score()
    ..bonus(10)
    ..bonus(15);
  print(score.points);
}
output
25

Example 5 — Computed Getters

dart
class Square {
  double side;

  Square(this.side);

  double get area => side * side;
  double get perimeter => side * 4;
}

void main() {
  var tile = Square(3);
  print('Area: ${tile.area}');
  print('Perimeter: ${tile.perimeter}');
}
output
Area: 9.0
Perimeter: 12.0

Example 6 — A Getter/Setter Pair with an Arrow Setter

dart
class Timer {
  int seconds;

  Timer(this.seconds);

  int get minutes => seconds ~/ 60;
  set minutes(int value) => seconds = value * 60;
}

void main() {
  var timer = Timer(150);
  print(timer.minutes);
  timer.minutes = 3;
  print(timer.seconds);
}
output
2
180

A short setter can use => too. 150 ~/ 60 is 2 whole minutes.

Example 7 — Student Marks

dart
class Student {
  final String name;
  int storedMarks = 0;

  Student(this.name);

  int get marks => storedMarks;

  set marks(int value) {
    if (value < 0 || value > 100) {
      print('Invalid mark for $name: $value');
      return;
    }
    storedMarks = value;
  }

  bool get passed => storedMarks >= 50;
}

void main() {
  var zayan = Student('Zayan')
    ..marks = 120
    ..marks = 72;
  print('${zayan.name}: ${zayan.marks}, passed: ${zayan.passed}');
}
output
Invalid mark for Zayan: 120
Zayan: 72, passed: true

How it works

  1. A cascade can assign through a setter: ..marks = 120 calls set marks.
  2. 120 is rejected, so storedMarks stays 0 until 72 arrives.
  3. passed is worked out from the stored mark each time it is read.

Example 8 — Shopping Total

dart
class CartItem {
  final String product;
  final int price;
  int storedQuantity = 1;

  CartItem(this.product, this.price);

  int get quantity => storedQuantity;

  set quantity(int value) {
    if (value < 1) {
      print('Quantity must be at least 1');
      return;
    }
    storedQuantity = value;
  }

  int get total => price * storedQuantity;
}

void main() {
  var item = CartItem('Notebook', 500);
  item.quantity = 3;
  item.quantity = 0;
  print('${item.product} x ${item.quantity} = ${item.total}');
}
output
Quantity must be at least 1
Notebook x 3 = 1500

Example 9 — Bank Account Number

dart
class Account {
  final String number;
  double balance;

  Account(this.number, this.balance);

  String get masked => '****${number.substring(number.length - 4)}';
}

void main() {
  var account = Account('1234567890', 2500);
  print('Account ${account.masked}: ${account.balance}');
}
output
Account ****7890: 2500.0

How it works

  1. number is final, so it can never be changed after the account is created.
  2. masked is a read-only getter that shows only the last four digits.

Example 10 — Temperature

dart
class Weather {
  final String city;
  double celsius;

  Weather(this.city, this.celsius);

  double get fahrenheit => celsius * 9 / 5 + 32;
  set fahrenheit(double value) => celsius = (value - 32) * 5 / 9;

  bool get isFreezing => celsius <= 0;
}

void main() {
  var today = Weather('Murree', 5);
  print('${today.city}: ${today.fahrenheit} F');
  today.fahrenheit = 23;
  print('${today.celsius} C, freezing: ${today.isFreezing}');
}
output
Murree: 41.0 F
-5.0 C, freezing: true

Common Beginner Mistakes

1. Forgetting this when a parameter shadows a field

dart
// void rename(String name) {
//   name = name; // ✗ assigns the parameter to itself
// }

There is no error and no warning — the field simply never changes. Write this.name = name;.

2. Putting parentheses on a getter

dart
// double get area() => width * height; // ✗
output
Error: A getter can't have formal parameters.
Try removing '(...)'.

3. Calling a getter like a method

dart
// print(room.area()); // ✗
output
Error: 'area' isn't a function or method and can't be invoked.

Read a getter like a field: room.area.

4. A getter that returns itself

dart
// double get balance => balance; // ✗ should return storedBalance
output
Unhandled exception:
Stack Overflow

The getter calls itself forever. dart analyze warns first: "The getter 'balance' recursively returns itself." A setter that assigns to its own name loops the same way. Store the value in a field with a different name.

5. Assigning to a getter-only property or a final field

dart
// room.area = 20; // ✗ no setter
output
Error: The setter 'area' isn't defined for the type 'Rectangle'.

Add a setter if the property really should be assignable.

6. Writing this. everywhere

this.name where name alone works earns the unnecessary_this hint. Keep this for the shadowing case.


Best Practices

  • Leave this out unless a parameter or local variable shadows the field.
  • Chain calls with cascades instead of making methods return this.
  • Use a getter for a value calculated from fields, so it is never out of date.
  • Keep getters quick and free of surprises — reading a property should not change anything.
  • Put validation in a setter, and route your own methods through it so the rule lives in one place.
  • Make a property read-only — final or getter-only — unless it truly needs to change.

Mini Challenge

Sara's room thermostat accepts target temperatures from 16 to 30 only. Write a Thermostat class with a target getter/setter pair (start at 22) and a mode getter that returns 'Heating' when the target is above 24, otherwise 'Normal'. Set 35, then 26.

Your program should print exactly:

output
Rejected: 35
Target: 26, mode: Heating

Try it yourself before opening the solution.

Show solution
dart
class Thermostat {
  int storedTarget = 22;

  int get target => storedTarget;

  set target(int value) {
    if (value < 16 || value > 30) {
      print('Rejected: $value');
      return;
    }
    storedTarget = value;
  }

  String get mode => storedTarget > 24 ? 'Heating' : 'Normal';
}

void main() {
  var thermostat = Thermostat()
    ..target = 35
    ..target = 26;
  print('Target: ${thermostat.target}, mode: ${thermostat.mode}');
}
output
Rejected: 35
Target: 26, mode: Heating

Practice Questions

Work through these in order — they run from beginner to application to challenge.

Beginner

  1. Write a Book class with a title field and a method show() that prints Book: <title> without using this. Create Book('Dart Basics') and call show().

  2. Write a Circle class with a radius field and a getter diameter. Print the diameter of Circle(7).

  3. This method never changes the field. Fix it with one word, so the program prints Sara:

    dart
    class Player {
      String name;
    
      Player(this.name);
    
      void rename(String name) {
        name = name;
      }
    }
    
    void main() {
      var player = Player('Ali');
      player.rename('Sara');
      print(player.name);
    }

Application

  1. Write a Product class with price and quantity fields and a getter total. Print the total for price 250, quantity 4.
  2. Write a Distance class that stores meters and has a kilometers getter/setter pair. Start at 1500 meters, print the kilometers, then set kilometers = 2 and print meters.
  3. Write a Student class with a marks getter and a setter that rejects values above 100 by printing Rejected. Set 105, then 88, and print the marks.
  4. Write a Glass class with an amount field and a method pour(int ml) that adds to it. Use a cascade to pour 100, 50, and 25 into a new glass, then print the amount.

Challenge

  1. Write a Wallet class with a read-only final field owner, a balance property that cannot go below 0, and methods add and spend that go through the setter. For Zayan, add 500, spend 200, spend 400, then print the owner and balance.
  2. Write a Temperature class that stores celsius and has a getter label returning 'Cold' below 10, 'Warm' from 10 to 29, and 'Hot' from 30. Print the label for 5, 20, and 35.
  3. Write a Basket class whose method add(int price) returns this, plus an itemCount field and a total field. Chain three calls — 250, 100, 650 — and print Items: 3, total: 1000. Then explain in one sentence why a cascade would let add return void.

Practice Answers

Question 1 — Solution

dart
class Book {
  String title;

  Book(this.title);

  void show() {
    print('Book: $title');
  }
}

void main() {
  Book('Dart Basics').show();
}
output
Book: Dart Basics

Question 2 — Solution

dart
class Circle {
  double radius;

  Circle(this.radius);

  double get diameter => radius * 2;
}

void main() {
  print(Circle(7).diameter);
}
output
14.0

Question 3 — Solution

Add this., so the left side means the field:

dart
class Player {
  String name;

  Player(this.name);

  void rename(String name) {
    this.name = name;
  }
}

void main() {
  var player = Player('Ali');
  player.rename('Sara');
  print(player.name);
}
output
Sara

Question 4 — Solution

dart
class Product {
  int price;
  int quantity;

  Product(this.price, this.quantity);

  int get total => price * quantity;
}

void main() {
  print(Product(250, 4).total);
}
output
1000

Question 5 — Solution

dart
class Distance {
  double meters;

  Distance(this.meters);

  double get kilometers => meters / 1000;
  set kilometers(double value) => meters = value * 1000;
}

void main() {
  var walk = Distance(1500);
  print(walk.kilometers);
  walk.kilometers = 2;
  print(walk.meters);
}
output
1.5
2000.0

Question 6 — Solution

dart
class Student {
  int storedMarks = 0;

  int get marks => storedMarks;

  set marks(int value) {
    if (value > 100) {
      print('Rejected');
      return;
    }
    storedMarks = value;
  }
}

void main() {
  var student = Student();
  student.marks = 105;
  student.marks = 88;
  print(student.marks);
}
output
Rejected
88

Question 7 — Solution

dart
class Glass {
  int amount = 0;

  void pour(int ml) {
    amount += ml;
  }
}

void main() {
  var glass = Glass()
    ..pour(100)
    ..pour(50)
    ..pour(25);
  print(glass.amount);
}
output
175

Question 8 — Solution

dart
class Wallet {
  final String owner;
  int storedBalance = 0;

  Wallet(this.owner);

  int get balance => storedBalance;

  set balance(int value) {
    if (value < 0) {
      print('Not enough money');
      return;
    }
    storedBalance = value;
  }

  void add(int amount) {
    balance = balance + amount;
  }

  void spend(int amount) {
    balance = balance - amount;
  }
}

void main() {
  var wallet = Wallet('Zayan')
    ..add(500)
    ..spend(200)
    ..spend(400);
  print('${wallet.owner}: ${wallet.balance}');
}
output
Not enough money
Zayan: 300

Question 9 — Solution

dart
class Temperature {
  double celsius;

  Temperature(this.celsius);

  String get label {
    if (celsius < 10) return 'Cold';
    if (celsius < 30) return 'Warm';
    return 'Hot';
  }
}

void main() {
  print(Temperature(5).label);
  print(Temperature(20).label);
  print(Temperature(35).label);
}
output
Cold
Warm
Hot

Question 10 — Solution

dart
class Basket {
  int itemCount = 0;
  int total = 0;

  Basket add(int price) {
    itemCount++;
    total += price;
    return this;
  }
}

void main() {
  var basket = Basket().add(250).add(100).add(650);
  print('Items: ${basket.itemCount}, total: ${basket.total}');
}
output
Items: 3, total: 1000

A cascade always gives back the object it started on, whatever add returns — so add would not need to return this at all.


Quick Revision

  1. What does this refer to inside a method?
  2. When is this. required?
  3. What does the unnecessary_this hint tell you?
  4. Why are cascades usually better than methods that return this?
  5. How do you read a getter — with or without parentheses?
  6. How many parameters does a setter take?
  7. Why does a validating setter need a field with a different name?
  8. Name two ways to make a property read-only.

Key Takeaways

  • this means "the current object"; inside a method it is usually optional.
  • Use this. when a parameter shadows a field — without it, name = name silently does nothing.
  • A method can return this for chaining, but cascades do the same job on any class.
  • A getter (double get area => ...) is calculated on every read and has no parentheses.
  • A setter (set marks(int value) { ... }) runs on assignment and can reject bad values.
  • A getter and setter with the same name form one property, backed by a differently named field.
  • A getter with no setter, or a final field, is read-only.
  • A getter or setter that uses its own name loops forever.

What's Next?

Lecture 057 — Private Members & Encapsulation. Our validating setters have a back door: ali.storedMarks = -40 skips the check entirely. Next we will learn how a leading _ makes a member private, so the only way in is through the getters, setters, and methods you choose.


Video lecture

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