Laptop on a desk showing programming code on the screen

Dart Getters and Setters for Beginners: Guard Your Data

You made a Rectangle class last week, gave it a public width and height, and everything worked. Then a real requirement showed up. You need the area, and it has to stay correct after someone changes the width. You also need to stop anyone from setting a negative width, because a room that is minus two meters wide is a bug waiting to crash something downstream. The clumsy fix is to add getArea() and setWidth() methods and hope everyone remembers to call them. Dart has a cleaner answer. Getters and setters let you run code every time a value is read or written, while callers still use plain rectangle.area and rectangle.width = 5 like ordinary fields. By the end of this post you will be able to write a computed getter, guard a value with a setter, and know when to skip both.

What Dart getters and setters actually do

A getter is a method you call without parentheses. It looks like a field to whoever uses it, but it runs code to produce its value. That is the whole trick. Here is the smallest useful one: a rectangle that can tell you its own area.

class Rectangle {
  double width;
  double height;

  Rectangle(this.width, this.height);

  double get area => width * height;
}

void main() {
  var room = Rectangle(4, 3);
  print(room.area); // 12.0
}

Look at double get area => width * height;. The get keyword marks it as a getter, area is the name callers will use, and the arrow gives the value back. Down in main, you write room.area with no parentheses, exactly the way you would read a plain field. But area is not stored anywhere. It is worked out fresh every time you ask, from whatever width and height hold right then. Change the width to 5 and read room.area again and you get 15.0, no extra bookkeeping. That is the payoff of a computed getter: there is no second copy of the answer to fall out of sync with the real data. One small Dart note if you are coming from Java: you do not write room.getArea(). Dart folds that idea into the language, so a getter reads like the thing it represents.

Laptop screen showing Dart class code in an editor
A getter reads like a field but runs your code every time you ask.

A setter that guards your data

Getters read. Setters write, and the reason to bother writing one is to check the value before it lands. This is where you stop that negative width at the door. To do it, you hide the real field behind an underscore, which is how Dart marks something private to its file, and expose a getter and setter pair with the public name.

class Rectangle {
  double _width;
  double height;

  Rectangle(this._width, this.height);

  double get width => _width;

  set width(double value) {
    if (value < 0) {
      throw ArgumentError('Width cannot be negative: $value');
    }
    _width = value;
  }

  double get area => _width * height;
}

void main() {
  var room = Rectangle(4, 3);
  room.width = 5;
  print(room.area); // 15.0

  room.width = -2; // throws
}

The field is now _width, and nothing outside this file can touch it directly. The setter, set width(double value), catches every assignment. When you write room.width = 5, Dart hands the 5 to value, the check passes, and it stores it. When you write room.width = -2, the check fails and Dart stops with a message you will actually see:

Unhandled exception:
Invalid argument(s): Width cannot be negative: -2.0

That error is the point. The bad value never made it into the object, so nothing downstream has to defend against a negative width. The caller still writes room.width = 5, the same line they would write for a plain field, and has no idea a guard is standing behind it. If the private underscore business feels fuzzy, the Dart classes post covers how fields and constructors fit together first.

Throwing is not your only move. If a bad value should be quietly corrected instead of rejected, clamp it: write _width = value < 0 ? 0 : value; inside the setter and a negative width becomes zero without stopping the program. Throw when the caller made a mistake they need to hear about. Clamp when you can reasonably fix it for them. Either way the check lives in exactly one place, so you write it once and every future assignment gets it for free.

Getters that compute instead of store

Once the pattern clicks, you will reach for getters any time a value can be worked out from data you already have. Storing it would just be a second thing to keep updated. Add a couple more to the rectangle and watch how natural they read.

class Rectangle {
  double width;
  double height;

  Rectangle(this.width, this.height);

  double get area => width * height;
  double get perimeter => 2 * (width + height);
  bool get isSquare => width == height;
}

void main() {
  var tile = Rectangle(5, 5);
  print(tile.area);      // 25.0
  print(tile.perimeter); // 20.0
  print(tile.isSquare);  // true
}

Three getters, three different return types, and every one reads like a property the rectangle simply has. tile.perimeter gives 20.0, tile.isSquare gives true, and both are computed the instant you ask. Resize the tile and they update themselves, because there is nothing to update. This is also the honest answer to a question beginners ask about whether to store or compute a value. If you can derive it cheaply from other fields, make it a getter. You remove a whole class of bugs where the stored copy and the source data drift apart. In CIS225 this is usually the moment students stop reaching for a calculateArea() method and start letting the object describe itself.

Developer desk with notebook and code notes for planning a class
Compute a value with a getter instead of storing a copy that can drift out of date.

When to skip getters and setters

Here is the trap, and almost everyone walks into it once. You read that getters and setters are good practice, so you wrap every single field in a pair that does nothing but read and write. That is busywork, and Dart makes it pointless.

// Needless: this getter and setter add nothing
class Point {
  double _x;
  Point(this._x);

  double get x => _x;
  set x(double value) => _x = value;
}

// Fine: a public field does exactly the same thing
class Point {
  double x;
  Point(this.x);
}

The two versions behave identically, so the second one wins by being shorter. Reach for a getter or setter when it earns its place, meaning the getter computes something or the setter checks something. A plain pass-through pair is just a longer way to write a public field. The reason this feels safe to skip in Dart, and does not in older languages, is that you can start with a public field and switch to a getter and setter later without changing a single line of calling code. The caller wrote point.x and point.x = 4 either way. So you are never locked in. Start simple with a public field, and upgrade to a getter or setter the day you actually need to compute or validate. If you are still shaky on how objects hold their data, the Dart inheritance post and the Dart null safety post both build on the same foundation.

Your next step

Here is the short version worth keeping. A getter runs code but reads like a field, so use one for any value you can compute from data you already hold. A setter runs code on assignment, so use one to validate before a bad value gets stored, hiding the real field behind an underscore. And skip both when the pair would only read and write, because a public field does that already and you can always upgrade later without touching your callers. The uniform access is the quiet win: rectangle.area and rectangle.width = 5 look the same whether a field or a getter sits behind them.

The way to make this stick is to build one. Write a BankAccount class with a private _balance, a getter that returns it, and a deposit setter that refuses any amount below zero. Then add a computed getter called isOverdrawn that returns true when the balance is negative, and print it before and after a deposit. Once that feels natural, the Dart classes post is worth a second read, since getters and setters live on the constructors you already know. For the full reference, the Dart language guide on getters and setters lists the exact syntax. Open your editor and give your objects something worth guarding.

Leave a Comment

Your email address will not be published. Required fields are marked *