💉 Dependency Injection

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

  1. Register in modules - Keep dependencies organized
  2. Use singletons for services - Database, API clients
  3. Use factories for data - Models, DTOs
  4. Use keys when needed - Multiple instances of same type
  5. Keep modules focused - One module, one feature

Need more details? Check out the Key System section below.