Dart Fundamentals: The Language Behind Flutter · Classes, Modern Dart 3 Features and Async
Classes and Constructors
Define classes with fields, methods and getters, and use Dart's constructor shortcuts: this.field parameters, named constructors, initializer lists, const constructors, factory constructors and copyWith.
What you'll build
A Temperature value class and a Todo model like the ones you'd use in a Flutter app: immutable fields, named constructors, a factory that parses JSON-like maps, and copyWith for updates.
The idea
- Fields and methods live in a class; getters compute values (
double get fahrenheit => ...). this.fieldconstructor parameters assign fields directly:Todo(this.title, {this.done = false});.- Named constructors offer alternative ways to create objects:
Temperature.fromFahrenheit(98.6). - Initializer lists (
: celsius = (f - 32) * 5 / 9) set final fields before the body runs. constconstructors create compile-time constants when all fields arefinal.factoryconstructors can return an existing instance or a subtype, or parse input (Todo.fromMap(json)).- Private members start with
_(private to the library/file).
For immutable models (common in Flutter state management), keep fields final and add a copyWith method that returns a modified copy.
The code
class Temperature {
final double celsius;
const Temperature(this.celsius);
Temperature.fromFahrenheit(double f) : celsius = (f - 32) * 5 / 9; // initializer list
double get fahrenheit => celsius * 9 / 5 + 32;
bool get isFreezing => celsius <= 0;
@override
String toString() => '${celsius.toStringAsFixed(1)}°C';
}
class Todo {
final String id;
final String title;
final bool done;
const Todo({required this.id, required this.title, this.done = false});
factory Todo.fromMap(Map<String, Object?> map) {
final title = map['title'];
if (title is! String || title.trim().isEmpty) {
throw FormatException('todo needs a title: $map');
}
return Todo(id: '${map['id']}', title: title.trim(), done: map['done'] == true);
}
Todo copyWith({String? title, bool? done}) =>
Todo(id: id, title: title ?? this.title, done: done ?? this.done);
@override
String toString() => '[${done ? 'x' : ' '}] $title';
}
void main() {
const freezing = Temperature(0);
final fever = Temperature.fromFahrenheit(100.4);
print('$freezing is ${freezing.fahrenheit}°F, freezing: ${freezing.isFreezing}');
print('fever: $fever');
final todo = Todo.fromMap({'id': 1, 'title': ' Buy coffee ', 'done': false});
final finished = todo.copyWith(done: true);
print('$todo -> $finished (original unchanged: ${todo.done == false})');
try {
Todo.fromMap({'id': 2, 'title': ''});
} on FormatException catch (e) {
print('rejected: ${e.message}');
}
print(identical(const Temperature(21), const Temperature(21))); // const objects are canonicalised
}
Output
0.0°C is 32.0°F, freezing: true
fever: 38.0°C
[ ] Buy coffee -> [x] Buy coffee (original unchanged: true)
rejected: todo needs a title: {id: 2, title: }
trueRun this on your computer — see “Get Started” for setup.
Common mistakes
- Mutable public fields in models, which make state changes hard to track; prefer
finalfields andcopyWith. - Forgetting
@override toString(), so prints showInstance of 'Todo'. - Using a regular constructor to parse input. A
factorycan validate and throw before building. - Equality. Two
Todos with the same fields aren't==unless you override==andhashCode(or use records or a code generator).
Key points
this.fieldparameters, named constructors and initializer lists keep constructors short.constconstructors for immutable compile-time values;factoryfor parsing and caching.- Getters compute derived values;
_nameis library-private. - Immutable models +
copyWithare the standard Flutter style.
Further reading: Classes, Constructors.
Practice questions
What is a factory constructor in Dart, and when would you use one?
Answer
A constructor marked factory that doesn't have to create a new instance of exactly its class: it can return a cached instance, a subtype, or validate/parse input before constructing (e.g. Todo.fromMap(json)).
Why do Flutter apps often use immutable model classes with copyWith?
Answer
Immutable objects can't change behind your back, which makes state changes explicit and easy to compare. copyWith returns a modified copy, so updating state means replacing objects rather than mutating them.
Open in the app to mark lessons complete, take notes and track your progress.