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
EasyLet'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 aConfigLoaderclass that simulates loading configuration settings asynchronously. Your class should have:- Three final fields:
appName(String),version(String), andmaxUsers(int) - A private constructor that initializes all three fields
- A static async factory method called
loadthat takes aString configIdparameter and returns aFuture<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"appendedversion:"1.0."followed by the length of configIdmaxUsers: the length of configId multiplied by 10
- Return a new
ConfigLoaderinstance with these values
- Use
- A
displayInfo()method that prints the configuration in this format:Config: [appName] v[version] (max [maxUsers] users)
- Three final fields:
main.dart: Import your config loader and demonstrate the async initialization pattern:- Print
Loading configurations... - Load a configuration with configId
"Production"and call itsdisplayInfo() - Load a configuration with configId
"Dev"and call itsdisplayInfo() - Print
All configs loaded!
- Print
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!"
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesLibraries & ImportsIntroduction to OOPClasses vs ObjectsThe this KeywordMethodsInstance VariablesConstructor BasicsRecap - Simple Calculator4Null Safety
Intro to Null SafetyNullable vs Non-NullableThe ? and ! OperatorsLate Keyword & Null SafetyNull-Aware OperatorsNull Safety in ClassesRecap - User Profile System7Abstract Classes & Interfaces
Abstract ClassesAbstract MethodsInterfaces in DartImplicit InterfacesImplementing vs ExtendingMultiple InterfacesRecap - Shape Calculator10Collections & Generics
List, Set, Map OverviewType-Safe CollectionsGeneric ClassesGeneric MethodsGeneric ConstraintsIterable & IteratorRecap - Generic Storage13Advanced OOP Concepts
Composition vs InheritanceExtension MethodsCallable ClassesSealed Classes (Dart 3)Records (Dart 3)Patterns & Matching (3.0)Enums with Methods16Project: Library Management
Project OverviewBook and User Classes2Constructors in Dart
Default ConstructorNamed ConstructorsInitializer ListsConstant ConstructorsFactory ConstructorsRedirecting ConstructorsRecap - Shape Builder5Encapsulation
Public vs Private MembersThe _ Prefix ConventionLibrary-Level PrivacyGetters & Setters DepthInformation HidingRecap - Student Records8Mixins
Introduction to MixinsCreating MixinsUsing Multiple Mixinson Keyword in MixinsMixin vs InheritanceMixin vs InterfaceRecap - Animal System11Special Methods
toString() OverridehashCode & == OverrideComparable Interfacecall() MethodnoSuchMethod OverrideRecap - Custom Collection14Design Patterns Part 1
Intro to Design PatternsSingleton PatternFactory PatternObserver PatternStrategy Pattern3Class Properties
Instance vs Static MembersFinal & Const FieldsLate VariablesStatic Methods & FieldsGetters and SettersRecap - Bank Account Manager6Inheritance
Basic InheritanceThe super KeywordMethod OverridingThe @override AnnotationThe final Class KeywordConstructors & InheritanceRecap - Employee Hierarchy9Polymorphism
Polymorphism BasicsPolymorphism via InterfacesRuntime Type CheckingThe is & as OperatorsCovariant KeywordRecap - Payment Processor12Async OOP
Futures & async/awaitStreams BasicsStream ControllersAsync ConstructorsAsync in Class MethodsRecap - Data Fetcher