singleton_manager
A high-performance, zero-dependency singleton manager for Dart with built-in code generation annotations.
Features
- Type-safe: Full generic support with compile-time type checking
- High performance: O(1) registration and retrieval operations
- Zero runtime dependencies: No external package dependencies
- Flexible API: Register any Dart objects, not just
ISingletonimplementations (v0.3.3+) - Dependency Injection: Factory-based DI with
SingletonDIand static access withSingletonDIAccess(v0.2.0+, enhanced v0.3.0+) - Code Generation Annotations (v0.4.0+): Built-in
@isSingletonand@isInjectedannotations for automatic DI setup - Generic Registry (v0.6.0+): Compound
(Type, Key)keyed registry with eager/lazy entries, versioning, and strict duplicate detection - Optional Lifecycle Management:
ISingletoninterface for initialization and cleanup (optional, v0.2.0+) - Lazy loading: Initialize singletons only when first accessed
- Multi-platform: Supports VM, Web, Native, and Flutter
- Pure Dart: No platform-specific code required
Installation
Add to your pubspec.yaml:
dependencies:
singleton_manager: ^0.6.0
Quick Start
Dependency Injection (v0.2.0+, v0.3.0 enhancements)
import 'package:singleton_manager/singleton_manager.dart';
// 1. Define your service
class UserService implements ISingleton<dynamic, void> {
@override
Future<void> initialize(dynamic input) async => print('init');
@override
void initializeDI() => print('di init');
}
void main() {
// 2. Register factory (factory-based DI)
SingletonDI.registerFactory<UserService>(UserService.new);
final manager = SingletonManager.instance;
manager.add<UserService>();
// OR register pre-configured instance (v0.3.0+)
final userService = UserService();
manager.addInstance<UserService>(userService);
// 3. Get service
final service = manager.get<UserService>();
// OR use static API (v0.3.0+)
SingletonDI.registerFactory<UserService>(UserService.new);
SingletonDIAccess.add<UserService>();
final svc = SingletonDIAccess.get<UserService>();
}
Basic Usage
import 'package:singleton_manager/singleton_manager.dart';
void main() {
// Create a manager
final manager = SingletonManager<String>();
// Register a singleton
manager.register('myService', () => MyService());
// Retrieve it (same instance always)
final service = manager.get('myService'); // Returns MyService
final serviceSame = manager.get('myService'); // Same instance
}
class MyService {
void doSomething() => print('Hello from Service!');
}
Lazy Loading
// Singleton only created when first accessed
manager.registerLazy('expensiveService',
() => ExpensiveResourceService()
);
Code Generation with Annotations (v0.4.0+)
Use @isSingleton and @isInjected annotations with singleton_manager_generator for automatic DI setup:
@isSingleton
class UserService {
@isInjected
late DatabaseConnection db;
@isInjected
late Logger logger;
}
void main() {
// Register dependencies
SingletonDI.registerFactory<DatabaseConnection>(() => DatabaseConnection());
SingletonDI.registerFactory<Logger>(() => Logger());
// Generator creates initializeDI() factory automatically
final service = UserService.initializeDI();
}
See singleton_manager_generator for setup instructions.
Compound-Key Registry (v0.6.0+)
Use the Registry<Key> mixin (or the ready-made RegistryManager / RegistryAccess) when you need multiple named instances of the same type — for example, environment-specific services:
import 'package:singleton_manager/singleton_manager.dart';
class DbConnection implements IValueForRegistry {
DbConnection(this.url);
final String url;
@override void destroy() {}
}
void main() {
// Static API via RegistryAccess (global RegistryManagerSingleton)
RegistryAccess.register<DbConnection>('prod', DbConnection('postgres://prod'));
RegistryAccess.register<DbConnection>('dev', DbConnection('postgres://dev'));
final prod = RegistryAccess.getInstance<DbConnection>('prod');
print(prod.url); // postgres://prod
// Replace an entry (throws RegistryNotFoundError if absent)
RegistryAccess.replace<DbConnection>('dev', DbConnection('postgres://dev2'));
// Lazy registration — factory called only on first access
RegistryAccess.registerLazy<DbConnection>('staging', () => DbConnection('postgres://staging'));
}
For per-instance registries, use RegistryManager directly:
final registry = RegistryManager();
registry.register<DbConnection>('primary', DbConnection('postgres://primary'));
Error handling
| Situation | Error thrown |
|---|---|
register / registerLazy on an existing key |
DuplicateRegistrationError |
replace / replaceLazy / getInstance on a missing key |
RegistryNotFoundError |
Entry types
| Class | Behaviour |
|---|---|
EagerEntry<V> |
Stores a pre-created instance |
LazyEntry<V> |
Stores a factory; instance created on first getInstance call |
ValueWithVersion<V> |
Wraps any entry with an integer version counter (incremented on each replace) |
Documentation
For more information, see the main project documentation.
Contributing
See CONTRIBUTING.md for contribution guidelines.
License
MIT License - see LICENSE file for details.