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.
// 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.
// 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.
// 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.
// 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.
// 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 bindingfactory
: Function that creates the instancesingleton
: Whether to treat as singleton (default: false)
Resolution Methods
resolve()
Resolves an instance of type T
, optionally with context.
// 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
.
// 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
.
// 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
.
// 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.
// 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
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
// 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');