16  Class Init Flow

class User {
  String name;
  String email;
  String displayName;
  DateTime createdAt;
  bool isActive;
  
  // Constructor with initializer list AND constructor body
  User(this.name, this.email) : displayName = name.toUpperCase() {
    // Constructor body - runs after initializer list
    createdAt = DateTime.now();
    isActive = true;
    print('User ${displayName} created at ${createdAt}');
  }
}

16.1 Class Structure Overview

User Class
├── Instance Variables
│   ├── name (String)
│   ├── email (String)
│   ├── displayName (String)
│   ├── createdAt (DateTime)
│   └── isActive (bool)
└── Constructor
    ├── Parameters: name, email
    ├── Initializer List: displayName = name.toUpperCase()
    └── Constructor Body: createdAt, isActive, print statement

16.2 Detailed Initialization Flow

When you create a new User object, here’s the complete sequence:

Step 1: Memory Allocation - Dart allocates memory for the new User instance - All instance variables are initially null/uninitialized

Step 2: Parameter Assignment - this.name = name (from parameter) - this.email = email (from parameter)

Step 3: Initializer List Execution - displayName = name.toUpperCase() runs - This happens before the constructor body - name is already available, so we can safely use it

Step 4: Constructor Body Execution - createdAt = DateTime.now() runs - isActive = true runs - print('User ${displayName} created at ${createdAt}') runs - All fields from initializer list are already available here

Step 5: Object Ready - All fields are now initialized and the object is ready to use

16.3 Visual Flow Diagram

User("Sarah", "sarah@email.com")
        ↓
   Memory allocated
   [name: null, email: null, displayName: null, createdAt: null, isActive: null]
        ↓
   Parameter assignment:
   this.name = "Sarah"
   this.email = "sarah@email.com"
        ↓
   Initializer list:
   displayName = "Sarah".toUpperCase()
   displayName = "SARAH"
        ↓
   Constructor body:
   createdAt = DateTime.now()           // e.g., 2025-07-18 14:30:25
   isActive = true
   print('User SARAH created at 2025-07-18 14:30:25')
        ↓
   Object ready:
   [name: "Sarah", email: "sarah@email.com", displayName: "SARAH", 
    createdAt: 2025-07-18 14:30:25, isActive: true]

16.4 Key Differences with Constructor Body

Initializer List vs Constructor Body:

  • Initializer List (: displayName = name.toUpperCase()):
    • Runs first
    • Direct field assignment
    • Must be compile-time deterministic expressions
    • Cannot contain complex logic or method calls
  • Constructor Body ({ ... }):
    • Runs after initializer list
    • Can contain any Dart code
    • Can call methods, use conditionals, loops
    • Can access all previously initialized fields

16.5 Usage Example

void main() {
  var user = User("Bob", "bob@example.com");
  // Output: User BOB created at 2025-07-18 14:30:25.123456
  
  print(user.name);        // "Bob"
  print(user.email);       // "bob@example.com"
  print(user.displayName); // "BOB"
  print(user.createdAt);   // 2025-07-18 14:30:25.123456
  print(user.isActive);    // true
}

16.6 When to Use Each Approach

Use Initializer List for: - Simple field calculations based on parameters - Required field initialization - final field initialization

Use Constructor Body for: - Complex initialization logic - Method calls - Conditional initialization - Logging or side effects (like our print statement)

This pattern is very common in Flutter for widgets and data models where you need both simple field mapping and complex setup logic.