Lesson 12 of 51 beginner

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?

Constructors are special methods that create and initialize objects. Dart gives you many flavors: default constructors for the common case, named parameters for clarity, named constructors for alternative creation paths, factory constructors for advanced logic, and const constructors for compile-time optimization. Understanding constructors is essential because Flutter widgets are constructed thousands of times during rebuilds.

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

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. 1. Define UserModel class -- this represents user data from the API
  2. 2. Declare final String id -- immutable unique identifier
  3. 3. Declare final String name -- immutable user name
  4. 4. Declare final String email -- immutable email address
  5. 5. Declare final DateTime createdAt -- immutable timestamp of account creation
  6. 6. Define a const constructor with required named parameters using this.field shorthand
  7. 7. The factory UserModel.fromJson constructor takes a Map parameter
  8. 8. It returns a new UserModel by extracting and casting each value from the JSON map
  9. 9. Parse the id field from json as a String
  10. 10. Parse the name field from json as a String
  11. 11. Parse the email field from json as a String
  12. 12. Parse created_at from json as a String and convert it to DateTime using DateTime.parse
  13. 13. The toJson method returns a Map for serialization
  14. 14. Include id, name, email as string values in the map
  15. 15. Convert createdAt to ISO 8601 string format for JSON compatibility
  16. 16. The copyWith method takes optional parameters for each field you might want to change
  17. 17. It returns a new UserModel with provided values or falls back to existing values using the ?? operator
  18. 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

Open interactive version (quiz + challenge) ← Back to course: Flutter & Dart