Service Container & Dependency Injection

The Dependency Injection (DI) container is a powerful tool for managing class dependencies and performing dependency injection.

Binding Methods

bind()

Registers a factory function for type T with optional singleton control.

dart
// Basic factory binding (new instance each time)
  container.bind<HttpClient>((c) => HttpClient());
  
  // Singleton factory binding
  container.bind<Database>((c) => Database.connect(), singleton: true);

Parameters:

  • factory: Function that creates the instance (receives container)
  • singleton: If true, same instance will be returned each time (default: false)

singleton()

Convenience method for registering a singleton binding.

dart
// Singleton database connection
  container.singleton<Database>((c) => Database.connect());
  
  // Singleton with dependencies
  container.singleton<AuthService>((c) => AuthService(
    userRepo: c.resolve<UserRepository>(),
    config: c.resolve<Config>(),
  ));

Equivalent to bind(factory, singleton: true)

lazySingleton()

Registers a singleton that's only instantiated on first resolution.

dart
// Lazy singleton logger
  container.lazySingleton<Logger>((c) => Logger(
    level: c.resolve<Config>().logLevel,
  ));
  
  // Heavy service that might not be used
  container.lazySingleton<ReportGenerator>((c) => ReportGenerator(
    database: c.resolve<Database>(),
  ));

Difference from regular singleton: Instantiation is deferred until first use

instance()

Registers an already created instance.

dart
// Pre-configured instance
  final analytics = AnalyticsService(apiKey: 'key-123');
  container.instance<AnalyticsService>(analytics);
  
  // Mock instance for testing
  final mockUserRepo = MockUserRepository();
  container.instance<UserRepository>(mockUserRepo);

Useful for:

  • Pre-configured objects
  • Mock instances in testing
  • Objects created outside DI system

bindWhen()

Registers a contextual binding that only applies for specific context keys.

dart
// Different payment providers
  container.bindWhen<PaymentGateway>(
    'stripe', 
    (c) => StripeGateway(c.resolve<Config>().stripeKey),
    singleton: true
  );
  
  container.bindWhen<PaymentGateway>(
    'paypal',
    (c) => PayPalGateway(c.resolve<Config>().paypalKey),
  );

Parameters:

  • context: String key that identifies this binding
  • factory: Function that creates the instance
  • singleton: Whether to treat as singleton (default: false)

Resolution Methods

resolve()

Resolves an instance of type T, optionally with context.

dart
// Basic resolution
  final db = container.resolve<Database>();
  
  // Contextual resolution
  final paymentGateway = container.resolve<PaymentGateway>('stripe');
  
  // With error handling
  try {
    final service = container.resolve<SomeService>();
  } catch (e) {
    // Handle missing binding
  }

Parameters:

  • context: Optional context key for contextual bindings

Returns: Instance of type T

Throws: Exception if no binding found

resolveAll()

Resolves all registered instances of type T.

dart
// Get all validators
  final validators = container.resolveAll<Validator>();
  
  // Process all middleware
  final middleware = container.resolveAll<Middleware>();
  await Future.wait(middleware.map((m) => m.process(request)));

Useful for:

  • Plugin systems
  • Middleware pipelines
  • Event handlers

Management Methods

has()

Checks if a binding exists for type T.

dart
// Check before resolving
  if (container.has<FeatureService>()) {
    final feature = container.resolve<FeatureService>();
  }
  
  // Contextual check
  if (container.has<PaymentGateway>('stripe')) {
    // Stripe-specific logic
  }

Returns: true if binding exists, false otherwise

unbind()

Removes the binding for type T.

dart
// Remove standard binding
  container.unbind<Logger>();
  
  // Remove contextual binding
  container.unbind<PaymentGateway>('stripe');
  
  // Testing pattern
  container.unbind<Database>();
  container.singleton<Database>(() => TestDatabase());

Useful for:

  • Testing scenarios
  • Runtime reconfiguration

flush()

Removes all bindings and resets the container.

dart
// Reset entire container
  container.flush();
  
  // Test setup example
  void setUpTests() {
    container.flush();
    registerTestBindings();
  }

Primary use case: Between tests to ensure clean state

Usage Patterns

Service Provider Example

dart
class AppServiceProvider {
    void register(ContainerInterface container) {
      // Singletons
      container.singleton<Config>((c) => Config.load());
      container.lazySingleton<Database>((c) => Database(
        config: c.resolve<Config>(),
      ));
      
      // Factories
      container.bind<HttpRequest>((c) => HttpRequest(
        client: c.resolve<HttpClient>(),
      ));
      
      // Contextual
      container.bindWhen<Cache>('redis', (c) => RedisCache());
    }
  }

Contextual Binding Example

dart
// Register different storage implementations
  container.bindWhen<Storage>(
    's3', 
    (c) => S3Storage(c.resolve<Config>().s3Config),
  );
  container.bindWhen<Storage>(
    'local', 
    (c) => LocalStorage('/storage'),
  );
  
  // Resolve based on context
  final storage = isProduction
    ? container.resolve<Storage>('s3')
    : container.resolve<Storage>('local');

On this page