Lesson 3 · 3 of 20
Getters, setters and private members · Dart
Protect the state of your Dart classes with private members (_), computed getters and validating setters, using a bank account as the example.

Dart has no private or public keywords: a name that starts with an underscore (_balanceCents, _history) is library-private, which means private to the file it lives in. Code in another file cannot read or write it; code in the same file can. That is why privacy really shows once a project grows into several files, with each class in its own.
Getters (get) are read like a field, without parentheses, but compute their value every time. balance returns the formatted balance and history returns an unmodifiable copy made with List.unmodifiable, so nobody outside can change the internal list: the attempt account.history.add('fake') throws an UnsupportedError, which the program catches.
A setter (set) is used like an assignment, account.dailyLimit = 20000, but runs code: here it checks that the limit is not negative and records the change in the history. With -1 it throws an ArgumentError and the previous value is kept. Having a getter and a setter with the same name makes dailyLimit look like an ordinary field from the outside.
main also shows the cascade operator ..: BankAccount('Lucia')..deposit(12000)..deposit(3550) calls two methods on the same object, and the whole expression evaluates to the account, not to whatever deposit returns. It is the everyday way to set up a freshly created object.
Code · main.dart
class BankAccount { final String owner; // A leading _ makes a name private to this file (its library). int _balanceCents = 0; int _dailyLimit = 50000; final List<String> _history = []; BankAccount(this.owner); // Getters read like fields but are computed on demand. String get balance => '${(_balanceCents / 100).toStringAsFixed(2)} EUR'; List<String> get history => List.unmodifiable(_history); int get dailyLimit => _dailyLimit; // A setter looks like an assignment, but can validate. set dailyLimit(int cents) { if (cents < 0) throw ArgumentError('limit cannot be negative'); _dailyLimit = cents; _history.add('limit $cents'); } void deposit(int cents) { _balanceCents += cents; _history.add('deposit $cents'); }}void main() { final account = BankAccount('Lucia')..deposit(12000)..deposit(3550); account.dailyLimit = 20000; // calls the setter print('${account.owner}: ${account.balance}, limit ${account.dailyLimit}'); try { account.dailyLimit = -1; } on ArgumentError catch (e) { print('Rejected: ${e.message}'); } try { account.history.add('fake'); } on UnsupportedError { print('Read-only: ${account.history}'); }}Output · dart run main.dart
Lucia: 155.50 EUR, limit 20000 Rejected: limit cannot be negative Read-only: [deposit 12000, deposit 3550, limit 20000]
Key points
_nameis private to the file (the library), not to the class.- A getter reads like a field and is computed on demand.
- A setter is used with
=and can validate the value. - Hand out unmodifiable copies of your internal lists.
..chains calls on the same object.
Exercise
Add a withdraw(int cents) method that throws a StateError if the amount exceeds the balance or the daily limit, and try to withdraw 30000 cents.