Encapsulation in Dart

By Syncfusion®, Inc. · Last updated

How to hide an object's data with private names, and control how it is read and changed with getters, setters and computed properties.

What Is Encapsulation?

Encapsulation = keeping an object's data inside the object, and letting outside code use it only through doors you choose.

  • The data is hidden. Outside code cannot change it directly.
  • The doors are getters (to read) and setters (to change).
  • A door can check a value before letting it in.

Simple lesson: encapsulation means "hide the data, and guard the doors".

A Real-World Example

Think of the school canteen card.

  • The card holds your money, but you cannot write a new amount on it with a pen.
  • To add money, the counter checks your payment first.
  • To spend, the machine checks that you have enough.
  • So the amount on the card is always correct.

Every Run box on this page runs on the course's Dart runner in your browser.

Privacy in Dart works per file (library). Each Run box is one file, so the examples show the idea and tell you what happens in a second file.

What You Will Learn

In this lesson, you will learn:

  1. Why we hide data, and what "controlled access" means.
  2. How the _ prefix makes a name private.
  3. How to write getters and read-only properties.
  4. How to check values in setters.
  5. How to make computed properties that are worked out on demand.

Data Hiding

Why Hide Data?

Here is a class with no protection:

Anyone can break the marks

class ReportCard {
  String name;
  int marks;
  ReportCard(this.name, this.marks);
}

void main() {
  var card = ReportCard('Asha', 92);
  card.marks = -40;
  print('${card.name}: ${card.marks}');
}
  • Marks can never be below 0, but the program accepted -40.
  • Any line, anywhere, can set any value.
  • In a big app, finding which line did it is very hard.

Controlled Access

Controlled access = outside code reads and changes data only through methods or properties that the class gives.

  • The class decides what can be read.
  • The class decides what can be changed, and how.
  • Bad values are stopped at the door, in one place.

Simple lesson: if only the class can change its data, the class can keep that data correct.

Public and Private Members

The _ Prefix

  • Public member = a normal name, like marks. Any file can use it.
  • Private member = a name that starts with _, like _marks. Only code in the same file can use it.
  • Dart has no private keyword. The _ is the whole rule.
  • You learned this rule for top-level names in Libraries. It works the same for fields and methods.

A private field with a public method

class PiggyBank {
  int _coins = 0;

  void add(int n) {
    if (n > 0) {
      _coins += n;
    }
  }

  int count() => _coins;
}

void main() {
  var bank = PiggyBank();
  bank.add(10);
  bank.add(-5);
  bank.add(3);
  print(bank.count());
}

add(-5) was ignored, because the method checks the value.

What Happens in Another File

If PiggyBank lives in piggy_bank.dart and your main() is in main.dart:

// main.dart
import 'piggy_bank.dart';

void main() {
  var bank = PiggyBank();
  bank._coins = 1000;   // Error
}

Real Dart stops this before running: The setter '_coins' isn't defined for the class 'PiggyBank'. From outside the file, _coins simply does not exist.

Inside the same file, _ names are still visible. That is why the Run boxes here can touch _coins from main().

In real projects, put each important class in its own file, so _ really hides the data.

Simple lesson: start a name with _ to keep it inside its file.

Getters

Writing a Getter

Getter = a method that you read like a field, with no ( ).

Type get name => value;

A getter for a private field

class Student {
  final String name;
  int _marks;

  Student(this.name, this._marks);

  int get marks => _marks;
}

void main() {
  var s = Student('Ravi', 75);
  print(s.marks);
  print('${s.name} scored ${s.marks}');
}
  • s.marks looks like a field, but it runs the getter.
  • There is no setter, so s.marks = 80; is an error.

Read-Only Properties

Read-only property = a value you can read but not change from outside.

Two ways to make one:

WayCodeWho can change it?
final fieldfinal String name;Nobody, after the constructor
private field + getterint _marks; and int get marks => _marks;Only the class itself

Trying to change a final field

class Student {
  final String rollNo;
  Student(this.rollNo);
}

void main() {
  var s = Student('7A-12');
  s.rollNo = '7A-99';
  print(s.rollNo);
}

Simple lesson: a getter lets outside code read a value without being able to change it.

Setters

Writing a Setter with Validation

Setter = a method that runs when you assign to a property: obj.name = value.

set name(Type value) { ... }

Try it here first. The field is private, and the setter checks every value:

A setter that guards the marks

class Student {
  final String name;
  int _marks = 0;

  Student(this.name);

  int get marks => _marks;

  set marks(int value) {
    if (value < 0 || value > 100) {
      print('Rejected $value: marks must be 0 to 100');
      return;
    }
    _marks = value;
  }
}

void main() {
  var s = Student('Priya');
  s.marks = 88;
  s.marks = 140;
  s.marks = -3;
  print('${s.name}: ${s.marks}');
}

Throwing Instead of Ignoring

Printing a message is gentle. Often it is better to stop with an error, so the mistake is not hidden:

A setter that throws

class Wallet {
  int _rupees = 0;

  int get rupees => _rupees;

  set rupees(int value) {
    if (value < 0) {
      throw ArgumentError('Money cannot be negative: $value');
    }
    _rupees = value;
  }
}

void main() {
  var w = Wallet();
  w.rupees = 250;
  print(w.rupees);
  w.rupees = -10;
  print('This line never runs');
}

You will learn to catch errors like this in Exception Handling.

Simple lesson: a setter is a guard at the door. It checks a value before saving it.

Computed Properties

Computed property = a getter that works out its value each time, from other fields. Nothing extra is stored.

Total, average and grade, worked out on demand

class ReportCard {
  final String name;
  final List<int> _marks;

  ReportCard(this.name, this._marks);

  int get total => _marks.fold(0, (a, b) => a + b);

  String get average => (total / _marks.length).toStringAsFixed(1);

  String get grade => total >= 240 ? 'A' : 'B';

  bool get passedAll => _marks.every((m) => m >= 35);
}

void main() {
  var card = ReportCard('Asha', [92, 85, 78]);
  print('Total: ${card.total}');
  print('Average: ${card.average}');
  print('Grade: ${card.grade}');
  print('Passed all: ${card.passedAll}');
}
  • total is never saved. It is added up each time you read it.
  • So it can never be out of date.
  • Keep getters fast and simple. A getter should not change anything.

Easy way to remember

  • _name = private: stays inside its file.
  • get = read door: obj.x
  • set = write door with a guard: obj.x = v
  • A getter with no matching setter = read-only.

Simple lesson: a computed property gives a fresh answer every time, from the data already stored.

TL;DR

  1. Encapsulation = hide the data, and allow access only through doors you choose.
  2. A name starting with _ is private to its file (library).
  3. Getter Type get x => ...; is read like a field.

    • A getter without a setter makes a read-only property.
  4. Setter set x(Type v) { ... } runs on obj.x = v, and can check the value.
  5. Computed property = a getter that works out its value from other fields.

Common mistakes

MistakeWrong ideaCorrect ideaWhat goes wrong
Writing private int marks;Dart has a private keywordStart the name with _A compile error.
Expecting _ to hide data in the same file_ hides from all code_ hides from other files onlyCode in the same file can still change it.
int get marks() => _marks;Getters have ( )A getter has no parameter listA compile error.
set marks(int v) => marks = v;The setter can set itselfSet the private field: _marks = vThe setter calls itself forever: Stack Overflow.
Storing total as a fieldIt must be savedUse a getter that adds it upThe saved total goes out of date.
A setter that does nothing on bad inputSilent is safeThrow an error, or tell the userBugs hide quietly.

Check Yourself

Exercises

What to Remember

  • Encapsulation hides the data and lets the class guard every change.
  • A _ name is private to its file. There is no private keyword.
  • A getter is read like a field. Without a setter, the value is read-only.
  • A setter can check a value before saving it, or throw an error.
  • A computed property works out its value each time, so it is never out of date.

Next: Inheritance. You will learn to build a new class on top of an existing one.