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:
- Why we hide data, and what "controlled access" means.
- How the
_prefix makes a name private. - How to write getters and read-only properties.
- How to check values in setters.
- 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
privatekeyword. 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.markslooks 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:
| Way | Code | Who can change it? |
|---|---|---|
final field | final String name; | Nobody, after the constructor |
| private field + getter | int _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}');
}
totalis 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.xset= 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
- Encapsulation = hide the data, and allow access only through doors you choose.
- A name starting with
_is private to its file (library). Getter
Type get x => ...;is read like a field.- A getter without a setter makes a read-only property.
- Setter
set x(Type v) { ... }runs onobj.x = v, and can check the value. - Computed property = a getter that works out its value from other fields.
Common mistakes
| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
Writing private int marks; | Dart has a private keyword | Start the name with _ | A compile error. |
Expecting _ to hide data in the same file | _ hides from all code | _ hides from other files only | Code in the same file can still change it. |
int get marks() => _marks; | Getters have ( ) | A getter has no parameter list | A compile error. |
set marks(int v) => marks = v; | The setter can set itself | Set the private field: _marks = v | The setter calls itself forever: Stack Overflow. |
Storing total as a field | It must be saved | Use a getter that adds it up | The saved total goes out of date. |
| A setter that does nothing on bad input | Silent is safe | Throw an error, or tell the user | Bugs 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 noprivatekeyword. - 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.