singleton_manager 2.1.1 copy "singleton_manager: ^2.1.1" to clipboard
singleton_manager: ^2.1.1 copied to clipboard

High-performance singleton manager for Dart with DI annotations and lazy loading.

singleton_manager #

Pub Version

A minimal, zero-dependency dependency-injection registry for Dart: RegistryManager, the IRegistry interface it implements, and the @dependencyInjectable annotation used by singleton_manager_generator to write factory constructors for you.

Features #

  • Type-safe: generic getInstance<T>() / connectInstance<Interface, Impl>()
  • Zero dependencies: no external package dependencies at runtime
  • Lazy: a connected factory runs once, the first time its type is resolved; the result is then cached
  • Per-key registration: register multiple instances of the same type under different string keys (default key: 'default')
  • Per-subkey disambiguation: register two instances of the same type under the same key — e.g. two implementations of one interface — via an independent subkey (default: 'default')
  • Code generation ready: annotate a class with @dependencyInjectable, let the generator write its dependencyInjectionFactory()
  • Pure Dart: no platform-specific code, works on VM, Web, and Flutter

Installation #

Add to your pubspec.yaml:

dependencies:
  singleton_manager: ^2.1.1

Quick Start #

import 'package:singleton_manager/singleton_manager.dart';

abstract class ILogger {
  void log(String message);
}

class ConsoleLogger implements ILogger {
  @override
  void log(String message) => print(message);
}

void main() {
  final registry = RegistryManager.instance;

  // Connect an interface to a factory — invoked lazily, once, on first access.
  registry.connectInstance<ILogger, ConsoleLogger>(() => ConsoleLogger());

  // Resolve it (same cached instance every time after the first call).
  final logger = registry.getInstance<ILogger>();
  logger.log('Hello from ConsoleLogger!');
}

The IRegistry API #

abstract class IRegistry {
  /// Returns the instance connected to [InterfaceType] under [key]/[subkey],
  /// creating it via its connected factory on first access. Throws
  /// `RegistryNotFoundError` if nothing is connected.
  InterfaceType getInstance<InterfaceType>({
    String key = 'default',
    String subkey = 'default',
  });

  /// Same as [getInstance] but returns `null` instead of throwing.
  InterfaceType? getInstanceNullable<InterfaceType>({
    String key = 'default',
    String subkey = 'default',
  });

  /// Connects [InterfaceType] to a concrete [InstanceType] via [factory].
  /// The factory runs once, the first time [getInstance] (or
  /// [getInstanceNullable]) is called for this (type, key, subkey) triple;
  /// the result is cached and reused after that.
  void connectInstance<InterfaceType, InstanceType extends InterfaceType>(
    InstanceType Function() factory, {
    String key = 'default',
    String subkey = 'default',
  });
}

RegistryManager is a singleton implementation of IRegistry, accessed via RegistryManager.instance. Every instance/factory is stored under the compound key (Type, key, subkey)key and subkey are independent axes, each defaulting to 'default'.

Per-key registration #

Use key to keep more than one instance of the same type — e.g. environment-specific connections:

final registry = RegistryManager.instance;

registry.connectInstance<IDbConnection, PostgresConnection>(
  () => PostgresConnection('postgres://prod'),
  key: 'prod',
);
registry.connectInstance<IDbConnection, PostgresConnection>(
  () => PostgresConnection('postgres://dev'),
  key: 'dev',
);

final prod = registry.getInstance<IDbConnection>(key: 'prod');
final dev = registry.getInstance<IDbConnection>(key: 'dev');

Per-subkey disambiguation #

Use subkey when you need two (or more) instances of the same interface, under the same key — e.g. a constructor with two parameters that share a type:

abstract class IStunHandler {
  String resolve();
}

final registry = RegistryManager.instance;

registry.connectInstance<IStunHandler, Ipv4StunHandler>(
  Ipv4StunHandler.new,
  subkey: 'ipv4',
);
registry.connectInstance<IStunHandler, Ipv6StunHandler>(
  Ipv6StunHandler.new,
  subkey: 'ipv6',
);

final ipv4Handler = registry.getInstance<IStunHandler>(subkey: 'ipv4');
final ipv6Handler = registry.getInstance<IStunHandler>(subkey: 'ipv6');

See example/3_subkey_dependency_injection.dart for the full pattern, including the @Subkey('...') annotation that lets singleton_manager_generator wire this up automatically.

Error handling #

Situation Result
getInstance<T>() and nothing was connected/set for (T, key, subkey) throws RegistryNotFoundError
getInstanceNullable<T>() and nothing was connected/set for (T, key, subkey) returns null

Code generation with @dependencyInjectable #

Annotate a class and run singleton_manager_generator to have its dependencyInjectionFactory({String key = 'default'}) written for you, instead of hand-writing the constructor call and its RegistryManager lookups:

@dependencyInjectable
class OrderService implements IOrderService {
  OrderService(this._repository, {IClock? clock});

  final IOrderRepository _repository;
}

Tag a constructor parameter with @Subkey('...') when it shares an interface type with another parameter, so each is resolved under its own subkey instead of colliding on 'default':

@dependencyInjectable
class DualStunHandler {
  DualStunHandler({
    @Subkey('ipv4') IStunHandler? ipv4Handler,
    @Subkey('ipv6') IStunHandler? ipv6Handler,
  });
}

Tag the class itself with @DependencyInjectable(subkey: '...') instead of the plain @dependencyInjectable shorthand when the generated --registry-output registry must connect that class under a specific subkey — typically because two @dependencyInjectable classes implement the same interface and both need to be auto-wired:

@DependencyInjectable(subkey: 'eu')
class EuEndpoint implements IRegionEndpoint {}

@DependencyInjectable(subkey: 'us')
class UsEndpoint implements IRegionEndpoint {}

See singleton_manager_generator for how the generator maps constructor parameters to getInstance/ getInstanceNullable calls, and for the --registry-output flag that also generates a MainInjection<ProjectName> class (with overridable beforeRegisterAllSingletons<ProjectName> / registerAllSingletons<ProjectName> / afterRegisterAllSingletons<ProjectName> methods) wiring everything up.

Examples #

See the examples directory for complete, runnable examples:

  • 1_basic_registry.dart: RegistryManager, connectInstance, getInstance, getInstanceNullable
  • 2_dependency_injection_factory.dart: @dependencyInjectable and the generated dependencyInjectionFactory()
  • 3_subkey_dependency_injection.dart: @Subkey('...') disambiguating two constructor parameters that share an interface
cd packages/singleton_manager
dart run example/1_basic_registry.dart
dart run example/2_dependency_injection_factory.dart
dart run example/3_subkey_dependency_injection.dart

Documentation #

For more information, see the main project documentation.

Contributing #

See CONTRIBUTING.md for contribution guidelines.

License #

MIT License - see LICENSE file for details.

0
likes
0
points
585
downloads

Publisher

unverified uploader

Weekly Downloads

High-performance singleton manager for Dart with DI annotations and lazy loading.

Repository (GitHub)
View/report issues

Topics

#singleton #pattern #dependency-injection #service-locator #resource-management

License

unknown (license)

Dependencies

index_generator

More

Packages that depend on singleton_manager