Dart Tutorial

Dart Lesson 44 of 102 4 min read

Getters and Setters in Dart

Learn getters and setters in Dart: computed properties, validating values, private fields, lazy getters and best practices.

On this page

Getters and setters are methods that look like fields. A getter runs when you read a property, and a setter runs when you assign to it. They let a class control its data without changing how other code uses it.

Every field already has them #

When you declare a field, Dart creates an invisible getter, and a setter too unless the field is final. That is why you rarely need to write trivial ones.

class User {
  String name = 'Guest';   // getter and setter
  final int id = 1;        // getter only
}

void main() {
  var u = User();
  u.name = 'Hema';  // uses the setter
  print(u.name);    // uses the getter
  // u.id = 2;      // error: no setter
}
Hema

Custom getters: computed properties #

Use get for a value worked out from other fields. It is called without brackets.

class Rectangle {
  double width;
  double height;
  Rectangle(this.width, this.height);

  double get area => width * height;
  bool get isSquare => width == height;
}

void main() {
  var r = Rectangle(5, 4);
  print(r.area);
  r.width = 4;
  print(r.area);     // always up to date
  print(r.isSquare);
}
20.0
16.0
true

Nothing is stored, so area can never disagree with width and height.

Custom setters: validation #

A setter receives the new value and decides what to do with it. The usual pattern is a private field (its name starts with _) with a public getter and setter in front.

class Account {
  double _balance = 0;

  double get balance => _balance;

  set balance(double value) {
    if (value < 0) {
      throw ArgumentError('Balance cannot be negative');
    }
    _balance = value;
  }
}

void main() {
  var account = Account();
  account.balance = 500;
  print(account.balance);

  try {
    account.balance = -20;
  } catch (e) {
    print('Rejected: $e');
  }
}
500.0
Rejected: Invalid argument(s): Balance cannot be negative

Read-only from outside #

Provide a getter and no setter. Code outside the file can read the value but cannot change it.

class Counter {
  int _count = 0;

  int get count => _count;

  void increment() => _count++;
}

void main() {
  var c = Counter();
  c.increment();
  c.increment();
  print(c.count);
  // c.count = 10; // error: there is no setter named 'count'
}
2

Converting on the way in and out #

class Temperature {
  double celsius;
  Temperature(this.celsius);

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

void main() {
  var t = Temperature(0);
  print(t.fahrenheit);
  t.fahrenheit = 212;
  print(t.celsius);
}
32.0
100.0

Lazy getters #

If a value is expensive to compute and never changes, compute it once on first use. late final does this in a single line.

class Report {
  final List<int> data;
  Report(this.data);

  late final int total = _computeTotal();

  int _computeTotal() {
    print('Computing...');
    return data.fold(0, (a, b) => a + b);
  }
}

void main() {
  var report = Report([10, 20, 30]);
  print(report.total);
  print(report.total); // not computed again
}
Computing...
60
60

Best practices #

  • Do not wrap a field just because you can. int get age => _age; set age(int v) => _age = v; adds nothing over a public field. In Dart, switching a field to a getter later does not break callers.
  • Getters should be cheap and have no side effects. If it does heavy work or changes something, make it a method.
  • Use a getter for “what it is”, a method for “what it does”. list.length but list.sort().
  • Validate in setters, or better, make the class immutable and validate once in the constructor.
  • A setter without a getter is confusing. Use a method instead.

Try it yourself #

Write a Person class with a private _age. Add a getter, a setter that rejects ages below 0 or above 150, and a getter isAdult. Test the setter with a valid and an invalid age.

Practise in the playground Updated by Santosh Adhikari