Members

Domains: Dart

DO

DO initialize fields at their declaration when possible.

If a field doesn’t depend on any constructor parameters, it can and should be initialized at its declaration. It takes less code and makes sure you won’t forget to initialize it if the class has multiple constructors.

class Folder {
  final String name;
  final List<Document> contents;

  Folder(this.name) : contents = [];
  Folder.temp() : name = 'temporary'; // Oops! Forgot contents.
}
class Folder {
  final String name;
  final List<Document> contents = [];

  Folder(this.name);
  Folder.temp() : name = 'temporary';
}

Of course, if a field depends on constructor parameters, or is initialized differently by different constructors, then this guideline does not apply.

PREFER

PREFER using a final field to make a read-only property.

If you have a field that outside code should be able to see but not assign to, a simple solution that works in many cases is to simply mark it final.

class Box {
  final contents = [];
}
class Box {
  var _contents;
  get contents => _contents;
}

Of course, if you need to internally assign to the field outside of the constructor, you may need to do the “private field, public getter” pattern, but don’t reach for that until you need to.

CONSIDER

CONSIDER using => for simple members.

In addition to using => for function expressions, Dart also lets you define members with it. That style is a good fit for simple members that just calculate and return a value.

double get area => (right - left) * (bottom - top);

bool isReady(num time) => minTime == null || minTime <= time;

String capitalize(String name) =>
    '${name[0].toUpperCase()}${name.substring(1)}';

People writing code seem to love =>, but it’s very easy to abuse it and end up with code that’s hard to read. If your declaration is more than a couple of lines or contains deeply nested expressions—cascades and conditional operators are common offenders—do yourself and everyone who has to read your code a favor and use a block body and some statements.

Treasure openChest(Chest chest, Point where) {
  if (_opened.containsKey(chest)) return null;

  var treasure = Treasure(where);
  treasure.addAll(chest.contents);
  _opened[chest] = treasure;
  return treasure;
}
Treasure openChest(Chest chest, Point where) =>
    _opened.containsKey(chest) ? null : _opened[chest] = Treasure(where)
      ..addAll(chest.contents);

You can also use => on members that don’t return a value. This is idiomatic when a setter is small and has a corresponding getter that uses =>.

num get x => center.x;
set x(num value) => center = Point(value, center.y);

DON'T

DON’T wrap a field in a getter and setter unnecessarily.

In Java and C#, it’s common to hide all fields behind getters and setters (or properties in C#), even if the implementation just forwards to the field. That way, if you ever need to do more work in those members, you can without needing to touch the callsites. This is because calling a getter method is different than accessing a field in Java, and accessing a property isn’t binary-compatible with accessing a raw field in C#.

Dart doesn’t have this limitation. Fields and getters/setters are completely indistinguishable. You can expose a field in a class and later wrap it in a getter and setter without having to touch any code that uses that field.

class Box {
  var contents;
}
class Box {
  var _contents;
  get contents => _contents;
  set contents(value) {
    _contents = value;
  }
}

DON’T use this. except to redirect to a named constructor or to avoid shadowing.

JavaScript requires an explicit this. to refer to members on the object whose method is currently being executed, but Dart—like C++, Java, and C#—doesn’t have that limitation.

There are only two times you need to use this.. One is when a local variable with the same name shadows the member you want to access:

class Box {
  var value;

  void clear() {
    this.update(null);
  }

  void update(value) {
    this.value = value;
  }
}
class Box {
  var value;

  void clear() {
    update(null);
  }

  void update(value) {
    this.value = value;
  }
}

The other time to use this. is when redirecting to a named constructor:

class ShadeOfGray {
  final int brightness;

  ShadeOfGray(int val) : brightness = val;

  ShadeOfGray.black() : this(0);

  // This won't parse or compile!
  // ShadeOfGray.alsoBlack() : black();
}
class ShadeOfGray {
  final int brightness;

  ShadeOfGray(int val) : brightness = val;

  ShadeOfGray.black() : this(0);

  // But now it will!
  ShadeOfGray.alsoBlack() : this.black();
}

Note that constructor parameters never shadow fields in constructor initialization lists:

class Box extends BaseBox {
  var value;

  Box(value)
      : value = value,
        super(value);
}

This looks surprising, but works like you want. Fortunately, code like this is relatively rare thanks to initializing formals.

Similar pages

Page structure
Terms

Constructor

Getters and setters

Dart

Parameters

int

Set

Return values

Named constructor

Methods

Functions