wiretap

A lightweight, zero-dependency dependency injection library for Dart that provides capabilities without the complexity.

Read more about wiretap in the documentation

Core Philosophy

wiretap embraces simplicity and clarity. It provides a minimal API surface that enables sophisticated dependency management patterns while remaining easy to understand and maintain. The library is designed around the principle that dependency injection should enhance your code's architecture without introducing unnecessary overhead or complexity.

Key Benefits

  • Zero Dependencies*: Less dependencies means faster builds and reduced bundle size.
  • Lightweight: Minimal runtime overhead with a focused feature set.
  • Type Safe: Full compile-time type safety with generic support.
  • Declarative: Tokens can define their own default values, making dependencies self-documenting.
  • Robust: Built-in protection against circular dependencies.
  • Predictable Behavior: Strict provider ordering and override semantics. Easy to understand.
  • Framework Agnostic: Works with and without Flutter. Runs seamlessly across any dart code.
  • No Codegen: No build_runner or metadata annotations required.

*While wiretap would like to use no dependencies, the official meta package is still required to provide analyzer feedback. This is the only dependency in use though.

Design Patterns Enabled

Dependency Inversion Principle

Decouple high-level modules from low-level modules by depending on abstractions rather than concrete implementations.

final databaseToken = provide<Database>();
final userServiceToken = provide<UserService>((inject) => 
  UserServiceImpl(inject(databaseToken)));

Strategy Pattern

Dynamically select algorithms or behaviors at runtime through provider overrides.

final paymentProcessorToken = provide<PaymentProcessor>();

// Production context
final prodContext = WiretapContext(providers: [
  paymentProcessorToken.provideOverride((_) => StripeProcessor()),
]);

// Test context
final testContext = WiretapContext(providers: [
  paymentProcessorToken.provideOverride((_) => MockProcessor()),
]);

Service Locator Pattern

Centralize service discovery and configuration through the injection context.

final serviceLocator = WiretapContext(providers: [
  loggerToken.provideOverride((_) => FileLogger()),
  cacheToken.provideOverride((_) => RedisCache()),
  configToken.(AppConfig.fromEnvironment()),
]).inject;

Aggregated Strategy Pattern

Aggregate multiple implementations of the same interface for plugin architectures or event handling.

final pluginToken = createCollectionToken<Plugin>();
final context = WiretapContext(providers: [
  pluginToken.provideAdditional((_) => [AuthPlugin(), LoggingPlugin()]),
  pluginToken.provideAdditional((_) => [MetricsPlugin()]),
]);

Core Concepts

Tokens

Tokens serve as typed keys for dependency resolution. They define what can be injected and optionally provide fallback values.

// Token with no default
final apiClientToken = createToken<ApiClient>();

// Token with default implementation
final configToken = createToken<Config>((_) => Config.defaults());

// Static value token
final appNameToken = createValueToken('MyApp');

Providers

Providers define how dependencies are created and resolved. They support different modes for various use cases.

// Override provider (replaces any existing value)
final provider = apiClientToken.provideOverride(
  (inject) => HttpApiClient(inject(configToken))
);

// Additional provider (appends to multi-values)
final pluginProvider = pluginToken.provideAdditional(
  (_) => [CustomPlugin()]
);

Injection Context

The injection context holds all registered providers and manages dependency resolution with proper lifecycle and error handling.

final context = WiretapContext(providers: [
  configProvider,
  apiClientProvider,
  serviceProvider,
]);

final inject = context.inject;
final service = inject(serviceToken);

Factory Support

wiretap intentionally does not provide factory methods with runtime arguments out of the box. This keeps the API simple and predictable. When you need factory-like behavior, you can inject functions that create values:

// Instead of a complex factory system, inject a creator function
final userCreatorToken = provide<User Function(String name, int age)>(
  (_) => (name, age) => User(name: name, age: age)
);

// Use it in your code
final createUser = inject(userCreatorToken);
final user = createUser('Alice', 30);

Usage Examples

Basic Dependency Injection

import 'package:injection/injection.dart';

// Define tokens
final configToken = createValueToken(Config(apiUrl: 'https://api.example.com'));
final httpClientToken = createToken((_) => HttpClient());
final apiServiceToken = createToken((inject) => 
  ApiService(inject(httpClientToken), inject(configToken)));

// Create context and resolve dependencies
final context = WiretapContext();
final apiService = context.inject(apiServiceToken);

Testing with Mocks

// Production setup
final prodContext = WiretapContext();

// Test setup with mocks
final testContext = WiretapContext(providers: [
  httpClientToken.provideOverride((_) => MockHttpClient()),
  configToken.provideOverride((_) => TestConfig()),
]);

final testApiService = testContext.inject(apiServiceToken);

Plugin Architecture

final pluginToken = createCollectionToken<Plugin>();

final context = WiretapContext(providers: [
  pluginToken.provideAdditional((_) => [
    AuthenticationPlugin(),
    ValidationPlugin(),
  ]),
  pluginToken.provideAdditional((_) => [
    LoggingPlugin(),
  ]),
]);

final plugins = context.inject(pluginToken);
// plugins contains all registered plugins

Complex Dependency Chains

final databaseToken = createToken<Database>((_) => PostgresDatabase());
final repositoryToken = createToken<UserRepository>((inject) => 
  UserRepositoryImpl(inject(databaseToken)));
final serviceToken = createToken((inject) => 
  UserService(inject(repositoryToken)));
final controllerToken = createToken((inject) => 
  UserController(inject(serviceToken)));

final context = WiretapContext();
final controller = context.inject(controllerToken);

Libraries

wiretap