Dependency Injection
Manage your app's dependencies easily with built-in DI.
What Is Dependency Injection?
Instead of creating objects directly, you register them and let the system provide them when needed.
// Instead of this
final service = MyService();
// Do this
i.addSingleton<MyService>((i) => MyService());
final service = Modular.get<MyService>();Quick Start
1. Register Dependencies
class HomeModule extends Module {
@override
FutureBinds binds(Injector i) {
i.addSingleton<HomeController>((i)=> HomeController());
i.add<ApiService>((i)=> ApiService());
}
}2. Use Dependencies
class HomePage extends StatefulWidget {
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
late final controller = context.read<HomeController>();
//or
final controller = Modular.get<HomeController>();
@override
Widget build(BuildContext context) {
return Scaffold(/* ... */);
}
}Dependency Types
Singleton
One instance for the entire app:
i.addSingleton<DatabaseService>((i) => DatabaseService());Factory
New instance every time:
i.add<ApiClient>((i) => ApiClient());Lazy Singleton
Created only when first used:
i.addLazySingleton<HeavyService>((i)=> HeavyService());Async Dependencies
For dependencies that need async initialization:
class DatabaseModule extends Module {
@override
FutureOr<void> binds(Injector i) async {
// Wait for database connection
await Future.delayed(Duration(seconds: 1));
i.addSingleton<DatabaseService>((i)=> DatabaseService());
}
}Using Keys
Register multiple instances of the same type:
// Register with keys
i.addSingleton<ApiService>((i) => ApiService(), key: 'main');
i.addSingleton<ApiService>((i) => ApiService(), key: 'backup');
// Use with keys
final mainApi = Modular.get<ApiService>(key: 'main');
final backupApi = Modular.get<ApiService>(key: 'backup');Context Extension
Use the convenient context.read() extension:
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final service = context.read<MyService>();
return Text(service.getData());
}
}Real Example
class ProductsModule extends Module {
@override
FutureBinds binds(Injector i) {
i.addLazySingleton<ApiService>((i) => ApiService());
i.add<ProductsRepository>((i) => ProductsRepository(api: i.get<ApiService>()));
i.addSingleton<ProductsController>((i) => ProductsController(repository: i.get<ProductsRepository>()));
}
@override
List<ModularRoute> get routes => [
ChildRoute('/', child: (_, __) => ProductsPage()),
];
}
class ProductsPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final controller = context.read<ProductsController>();
return Scaffold(
body: ...,
);
}
}Best Practices
- Register in modules - Keep dependencies organized
- Use singletons for services - Database, API clients
- Use factories for data - Models, DTOs
- Use keys when needed - Multiple instances of same type
- Keep modules focused - One module, one feature
Need more details? Check out the Key System section below.