Lesson 10 / 25
Classes, Constructors and Initialiser Lists
Write classes with generative, named, const and factory constructors.
Constructors in many shapes
Dart classes have fields, methods, getters and setters. Constructors come in several forms. Generative constructors with initialising formal parameters assign fields concisely: Product(this.title, this.price);. Named constructors create alternatives: Product.free(this.title) : price = 0;. An initialiser list after a colon sets final fields and runs asserts before the body. const constructors (all fields final) allow compile-time constant instances, essential for efficient Flutter widgets. factory constructors can return cached instances, subclasses or instances built from data, such as factory Product.fromJson(Map<String, dynamic> json). Fields marked final must be set by the time the constructor finishes, and late final fields can be set once later. Private members start with an underscore (_secret) and are private to the library (the file and its parts), not the class. Getters (double get total => ...) compute values, and Dart provides == and hashCode only by identity unless you override them, which packages such as equatable or code generators can help with.
Ways to create an object
Generative, named, const and factory constructors produce instances in different ways.
A model class with several constructors
const, named and factory constructors with an initialiser list.
class Product {
const Product(this.sku, this.title, this.pricePaise)
: assert(pricePaise >= 0);
const Product.free(this.sku, this.title) : pricePaise = 0; // named constructor
factory Product.fromJson(Map<String, dynamic> json) { // factory constructor
return Product(
json['sku'] as String,
json['title'] as String,
(json['price_paise'] as num).toInt(),
);
}
final String sku;
final String title;
final int pricePaise;
String get displayPrice => 'Rs ${(pricePaise / 100).toStringAsFixed(2)}'; // getter
Map<String, dynamic> toJson() => {'sku': sku, 'title': title, 'price_paise': pricePaise};
@override
bool operator ==(Object other) =>
other is Product && other.sku == sku && other.pricePaise == pricePaise;
@override
int get hashCode => Object.hash(sku, pricePaise);
}
void main() {
const pen = Product('PEN', 'Blue pen', 4950);
final sample = Product.free('SAMPLE', 'Sample sachet');
final fromApi = Product.fromJson({'sku': 'INK', 'title': 'Ink', 'price_paise': 19900});
print(pen.displayPrice); // Rs 49.50
}Override == and hashCode together
If two objects are equal by ==, they must have the same hashCode, or sets and maps behave incorrectly. Object.hash(...) makes a correct implementation easy.
Quick check: What can a factory constructor do that a generative constructor cannot?
- Take parameters
- Initialise final fields
- Return an existing cached instance or a subtype instead of always creating a new instance of the class
- Be called with new
Answer
Return an existing cached instance or a subtype instead of always creating a new instance of the class — Factory constructors decide what to return, including cached objects or subclasses.