Constructors & Named Parameters
Multiple Ways to Build Your Objects
Open interactive version (quiz + challenge)Real-world analogy
Constructors are like different doors into the same building. The front door (default constructor) is the standard entrance. The side door (named constructor) is for special visitors. The factory door (factory constructor) has a bouncer who decides whether to let you into an existing room or build a new one. In team_mvp_kit, model classes use factory constructors to parse JSON from APIs, while entity classes use const constructors for efficiency.
What is it?
Dart named constructors and named parameters explained with copy-paste code — default, named, factory, and const constructors, plus fromJson and copyWith.
Real-world relevance
In team_mvp_kit, constructors are everywhere. Widget classes use const constructors with named parameters for readability: const HomeScreen(key: key). Model classes use factory UserModel.fromJson(json) to parse API responses from Dio. Entity classes use const constructors so identical entities can be reused in memory. BLoC classes use default constructors with dependency injection via GetIt.
Key points
- Default Constructor — The simplest constructor shares the class name. Dart's shorthand this.property automatically assigns parameters to properties, saving you from writing boilerplate assignment code.
- Named Parameters — Wrap parameters in curly braces to make them named. Named parameters are optional by default, so use required to enforce them. They make code readable -- you always know which value goes where.
- Default Values — Named parameters can have default values. If the caller does not provide a value, the default kicks in. This is great for configuration objects with sensible defaults.
- Named Constructors — Dart allows multiple constructors using ClassName.name syntax. Each named constructor can initialize the object differently. Common patterns include .empty(), .fromMap(), and .guest().
- Factory Constructors — A factory constructor can return an existing instance, a subtype, or compute the object before returning it. Unlike normal constructors, factory constructors have a body that must explicitly return an instance.
- Const Constructors — If all fields are final and known at compile time, mark the constructor as const. This lets Dart reuse identical objects instead of creating duplicates, saving memory.
- Initializer Lists — Code that runs before the constructor body, used to set final fields, assert preconditions, or call the super constructor. Initializer list entries are separated by commas after a colon.
- Redirecting Constructors — A constructor can delegate to another constructor in the same class using a colon and this(). This avoids duplicating initialization logic across multiple constructors.
- fromJson Factory Pattern — In team_mvp_kit, model classes use factory constructors named fromJson to parse API responses. This pattern converts raw Map data into typed Dart objects safely.
- toJson Method Pattern — Paired with fromJson, toJson converts an object back to a Map for sending to APIs or storing locally. Together they form the serialization/deserialization pattern used throughout team_mvp_kit.
Code example
class UserModel {
final String id;
final String name;
final String email;
final DateTime createdAt;
const UserModel({
required this.id,
required this.name,
required this.email,
required this.createdAt,
});
factory UserModel.fromJson(Map<String, dynamic> json) {
return UserModel(
id: json['id'] as String,
name: json['name'] as String,
email: json['email'] as String,
createdAt: DateTime.parse(json['created_at'] as String),
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'email': email,
'created_at': createdAt.toIso8601String(),
};
}
UserModel copyWith({String? name, String? email}) {
return UserModel(
id: id,
name: name ?? this.name,
email: email ?? this.email,
createdAt: createdAt,
);
}
}Line-by-line walkthrough
- 1. Define UserModel class -- this represents user data from the API
- 2. Declare final String id -- immutable unique identifier
- 3. Declare final String name -- immutable user name
- 4. Declare final String email -- immutable email address
- 5. Declare final DateTime createdAt -- immutable timestamp of account creation
- 6. Define a const constructor with required named parameters using this.field shorthand
- 7. The factory UserModel.fromJson constructor takes a Map parameter
- 8. It returns a new UserModel by extracting and casting each value from the JSON map
- 9. Parse the id field from json as a String
- 10. Parse the name field from json as a String
- 11. Parse the email field from json as a String
- 12. Parse created_at from json as a String and convert it to DateTime using DateTime.parse
- 13. The toJson method returns a Map for serialization
- 14. Include id, name, email as string values in the map
- 15. Convert createdAt to ISO 8601 string format for JSON compatibility
- 16. The copyWith method takes optional parameters for each field you might want to change
- 17. It returns a new UserModel with provided values or falls back to existing values using the ?? operator
- 18. For fields not in copyWith parameters like id and createdAt, the original values are always reused
Spot the bug
class Temperature {
final double celsius;
const Temperature(this.celsius);
factory Temperature.fromFahrenheit(double f) {
final c = (f - 32) * 5 / 9;
Temperature(c);
}
}Need a hint?
Look at what the factory constructor returns -- or does it?
Show answer
The factory constructor is missing a return statement. It creates a Temperature object but does not return it. Change Temperature(c); to return Temperature(c); -- factory constructors must explicitly return an instance.
Explain like I'm 5
Imagine you are ordering a custom pizza. The default constructor is like saying 'I want a pizza with pepperoni and cheese' -- you list everything in order. Named parameters are like a form: 'Size: Large, Topping: Mushroom, Crust: Thin' -- you label each choice so there is no confusion. A factory constructor is like a smart pizza shop that checks if someone already ordered the exact same pizza and gives you that one instead of making a new one. And a const constructor is like a pizza on display that never changes -- everyone sees the same one.
Fun fact
Dart's const constructors are so powerful that Flutter uses them to avoid rebuilding widgets that have not changed. When you write const Text('Hello'), Flutter knows that identical const objects are the same instance in memory -- so it can skip rebuilding entirely. This is one of the key performance tricks in Flutter.
Hands-on challenge
Create a Product class with: id, name, price, and an optional discount (default 0). Add a const constructor with named parameters. Add a factory Product.fromJson(Map) constructor. Add a toJson() method. Add a copyWith method that lets you change any field. Create a product from JSON, apply a discount with copyWith, and print the toJson result.
More resources
- Dart Constructors (dart.dev)
- Named Parameters in Dart (dart.dev)
- Factory Constructors Explained (dart.dev)