Menu
Coddy logo textTech

Async Constructors

Part of the Object Oriented Programming section of Coddy's Dart journey — lesson 79 of 110.

Constructors in Dart cannot be marked as async - they must return an instance of the class immediately, not a Future. So how do you create objects that need asynchronous initialization, like loading data from a database or fetching configuration?

The solution is to use a static async factory method. Keep the constructor private (or simple), and provide a static method that performs the async work and returns a Future of your class:

class UserProfile {
  final String name;
  final int age;
  
  UserProfile._(this.name, this.age);  // Private constructor
  
  static Future<UserProfile> create(String userId) async {
    // Simulate fetching data
    await Future.delayed(Duration(seconds: 1));
    return UserProfile._('Alice', 25);
  }
}

Future<void> main() async {
  var user = await UserProfile.create('123');
  print(user.name);  // Alice
}

The private constructor UserProfile._ handles the actual object creation, while the static create() method handles all the async work. Callers use await to get the fully initialized object.

This pattern ensures your object is completely ready to use once you have it - no partially initialized states or missing data. It's commonly used for classes that need to load resources, connect to services, or perform any setup that takes time.

challenge icon

Challenge

Easy

Let's build a configuration loader that demonstrates how to handle async initialization in classes! Since constructors can't be async, you'll use the static factory method pattern to create objects that need time to "load" their data.

You'll organize your code into two files:

  • config_loader.dart: Create a ConfigLoader class that simulates loading configuration settings asynchronously. Your class should have:
    • Three final fields: appName (String), version (String), and maxUsers (int)
    • A private constructor that initializes all three fields
    • A static async factory method called load that takes a String configId parameter and returns a Future<ConfigLoader>. This method should:
      • Use Future.delayed() with 100 milliseconds to simulate loading time
      • Generate the configuration values based on the configId:
        • appName: the configId with " App" appended
        • version: "1.0." followed by the length of configId
        • maxUsers: the length of configId multiplied by 10
      • Return a new ConfigLoader instance with these values
    • A displayInfo() method that prints the configuration in this format: Config: [appName] v[version] (max [maxUsers] users)
  • main.dart: Import your config loader and demonstrate the async initialization pattern:
    • Print Loading configurations...
    • Load a configuration with configId "Production" and call its displayInfo()
    • Load a configuration with configId "Dev" and call its displayInfo()
    • Print All configs loaded!

Notice how the static load method handles all the async work, while the private constructor simply assigns the values. Callers use await to get a fully initialized object!

Expected output:

Loading configurations...
Config: Production App v1.0.10 (max 100 users)
Config: Dev App v1.0.3 (max 30 users)
All configs loaded!

Cheat sheet

Constructors in Dart cannot be marked as async because they must return an instance of the class immediately, not a Future.

To create objects that need asynchronous initialization, use a static async factory method:

class UserProfile {
  final String name;
  final int age;
  
  UserProfile._(this.name, this.age);  // Private constructor
  
  static Future<UserProfile> create(String userId) async {
    // Perform async work here
    await Future.delayed(Duration(seconds: 1));
    return UserProfile._('Alice', 25);
  }
}

Usage:

Future<void> main() async {
  var user = await UserProfile.create('123');
  print(user.name);  // Alice
}

The private constructor (marked with _) handles object creation, while the static method handles async work. This ensures objects are fully initialized before use.

Try it yourself

import 'config_loader.dart';

void main() async {
  // TODO: Print "Loading configurations..."
  
  // TODO: Load a configuration with configId "Production"
  // and call its displayInfo() method
  
  // TODO: Load a configuration with configId "Dev"
  // and call its displayInfo() method
  
  // TODO: Print "All configs loaded!"
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming