An immutable data object pairs final fields with a const constructor. Each instance is frozen after construction, and the constructor can also be used in const contexts. A small method that reads the fields keeps presentation logic with the data.

Program

Play the program to build two Todo values and print their labels.

immutable_pattern.dart
class Todo {
  final String title;
  final bool done;
  const Todo(this.title, {this.done = false});
  String label() => '[${done ? 'x' : ' '}] $title';
}

void main() {
  final a = Todo('Write notes', done: true);
  final b = Todo('Buy milk');
  print('${a.label()} | ${b.label()}');
}
final fields `final String title; final bool done;` freeze the values after construction.
const constructor `const Todo(this.title, {this.done = false});` supports ordinary construction here and also allows compile-time constants when called with `const`.
named default `{this.done = false}` gives the named parameter a default; `Todo('Buy milk')` omits it.