💉 Dependency Injection
💉 Dependency Injection

💉 Dependency Injection

GoRouter Modular provides a powerful dependency injection system with automatic disposal to prevent memory leaks.

🔧 Basic Usage

Module Dependencies

class HomeModule extends Module {
  @override
  FutureOr<List<Bind<Object>>> binds() => [
    Bind.singleton<HomeController>((i) => HomeController()),
    Bind.factory<UserRepository>((i) => UserRepository()),
    Bind.lazySingleton<ApiService>((i) => ApiService()),
  ];
 
  @override
  List<ModularRoute> get routes => [
    ChildRoute('/', child: (context, state) => HomePage()),
  ];
}

Dependency Types

Singleton - One instance for the entire module lifecycle

Bind.singleton<HomeController>((i) => HomeController());

Factory - New instance every time

Bind.factory<UserRepository>((i) => UserRepository());

Lazy Singleton - Created only when first accessed

Bind.lazySingleton<ApiService>((i) => ApiService());

🔍 Retrieving Dependencies

Using Context

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final controller = context.read<HomeController>();
    final repository = context.read<UserRepository>();
    
    return Scaffold(
      body: Text('Hello ${controller.userName}'),
    );
  }
}

Using Modular

final controller = Modular.get<HomeController>();
final repository = Modular.get<UserRepository>();

Using Bind

final controller = Bind.get<HomeController>();
final repository = Bind.get<UserRepository>();

🔄 Module Lifecycle

Initialization

class AuthModule extends Module {
  @override
  void initState(Injector i) {
    // Initialize resources when module loads
    final authService = i.get<AuthService>();
    authService.initialize();
  }
 
  @override
  FutureOr<List<Bind<Object>>> binds() => [
    Bind.singleton<AuthService>((i) => AuthService()),
  ];
}

Disposal

class AuthModule extends Module {
  @override
  void dispose() {
    // The dispose method is used when you want to execute an action when the module is disposed,
    // for example, stop listening to a stream or disconnect from a websocket.
  }
}

⚡️ Asynchronous Binds

You can use asynchronous binds to initialize dependencies that require async operations, such as fetching remote configuration or initializing plugins.

Example: Remote Config (e.g., Firebase Remote Config)

class RemoteConfigModule extends Module {
  @override
  FutureOr<List<Bind<Object>>> binds() async {
    // Fetch remote config asynchronously
    final remoteConfig = await RemoteConfig.instance.fetchAndActivate();
    return [
      Bind.singleton<Dio>(
        (i) => Dio(BaseOptions(baseUrl: remoteConfig.baseUrl)),
      ),
    ];
  }
}
 
class SharedPreferencesModule extends Module {
  @override
  FutureOr<List<Bind<Object>>> binds() async {
    // Initialize SharedPreferences asynchronously
    final prefs = await SharedPreferences.getInstance();
    return [
      Bind.singleton<SharedPreferences>((i) => prefs),
      Bind.singleton<LocalStorageService>((i) => LocalStorageService(prefs)),
    ];
  }
}

:::warning ⚠️ Important Note In async binds() methods, avoid using Modular.get<T>() because the bind might not have been injected yet. Always use the Injector parameter or create dependencies directly. :::

:::warning Asynchronous binds are strictly forbidden in the AppModule. Only use async binds in feature modules. The root AppModule must always use synchronous binds to ensure proper app initialization and avoid unpredictable behavior. :::

🎯 Advanced Patterns

Dependency with Parameters

class UserModule extends Module {
  @override
  FutureOr<List<Bind<Object>>> binds() => [
    Bind.singleton<UserService>((i) => UserService(
      apiKey: 'your-api-key',
      baseUrl: 'https://api.example.com',
    )),
  ];
}

Dependent Dependencies

class AppModule extends Module {
  @override
  FutureOr<List<Bind<Object>>> binds() => [
    Bind.singleton<ApiService>((i) => ApiService()),
    Bind.singleton<UserRepository>((i) => UserRepository(
      apiService: i.get<ApiService>(), // Inject dependency
    )),
  ];
}

Conditional Dependencies

class AppModule extends Module {
  @override
  FutureOr<List<Bind<Object>>> binds() => [
    if (kDebugMode)
      Bind.singleton<Logger>((i) => DebugLogger())
    else
      Bind.singleton<Logger>((i) => ProductionLogger()),
  ];
}

🛡️ Memory Management

Automatic Disposal

  • Dependencies are automatically disposed when modules are unloaded
  • Prevents memory leaks
  • No manual cleanup required

📚 Related Topics