DMarlon Studio

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 · Getters, setters and private members in DMarlon Studio: the example's code and its output in the terminal

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

  • _name is 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.