Spot class abstract

Lightweight service locator for dependency injection.

Spot provides a minimal yet powerful DI framework with support for:

  • Singletons: Shared instance across the application
  • Factories: New instance on each request
  • Async Singletons: Asynchronous initialization support
  • Lifecycle Hooks: Automatic cleanup via SpotDisposable interface
  • Type Safety: Compile-time type checking with R extends T
  • Circular Dependency Detection: Runtime detection with helpful error messages
  • Testing Support: Comprehensive utilities via SpotTestHelper
  • Performance: Singleton caching for faster repeated access
  • Scoped Containers: Isolated dependency scopes via SpotContainer

Registration

Register dependencies during app initialization:

Spot.init((factory, single) {
  // Singleton - one instance shared across app
  single<ISettings, Settings>((get) => Settings());
  
  // Factory - new instance on each request
  factory<IRepository, Repository>((get) => Repository(get<Database>()));
  
  // Singleton with dependencies
  single<IApiClient, ApiClient>((get) => ApiClient(
    dio: get<Dio>(),
    settings: get<ISettings>(),
  ));
});

// Async singleton (requires async initialization)
Spot.registerAsync<Database, AppDatabase>((get) async {
  final db = AppDatabase();
  await db.initialize();
  return db;
});

Resolution

Inject dependencies using spot or spotAsync:

// Synchronous resolution
final settings = spot<ISettings>();
final repo = Spot.spot<IRepository>();

// Asynchronous resolution
final db = await spotAsync<Database>();
final api = await Spot.spotAsync<ApiClient>();

Lifecycle Management

Services implementing SpotDisposable are automatically cleaned up:

class ApiClient implements Disposable {
  final Dio dio;
  ApiClient(this.dio);
  
  @override
  void dispose() {
    dio.close();
  }
}

// Dispose specific service
Spot.dispose<ApiClient>();  // Calls dispose() automatically

// Dispose all services
Spot.disposeAll();  // Cleanup on app shutdown

Scoped Containers

Create isolated dependency scopes for tests or feature modules:

// Global dependencies
Spot.registerSingle<ISettings, Settings>((get) => Settings());

// Create test scope
final testScope = Spot.createScope();
testScope.registerSingle<ISettings, MockSettings>((get) => MockSettings());

// Use test scope (gets mock)
final testSettings = testScope.spot<ISettings>();

// Global scope unchanged (gets real implementation)
final globalSettings = spot<ISettings>();

// Cleanup test scope
testScope.dispose();

Testing

Use SpotTestHelper for isolated test environments:

test('with mocked dependencies', () async {
  await SpotTestHelper.runIsolated(() async {
    SpotTestHelper.registerMock<ISettings>(MockSettings());
    // Test runs with mock, original state restored after
  });
});

Features

  • Thread-Safe: Singleton initialization prevents race conditions
  • Performance: Caching for fast repeated singleton access
  • Debugging: printRegistry and isRegistered utilities
  • Error Messages: Detailed errors with registered type listings
  • Circular Detection: Clear error messages showing dependency cycles
  • Scoped Containers: Isolated scopes via createScope

See also:

Constructors

Spot()

Properties

hashCode int
The hash code for this object.
no setterinherited
runtimeType Type
A representation of the runtime type of the object.
no setterinherited

Methods

noSuchMethod(Invocation invocation) → dynamic
Invoked when a nonexistent method or property is accessed.
inherited
toString() String
A string representation of this object.
inherited

Operators

operator ==(Object other) bool
The equality operator.
inherited

Static Properties

isEmpty bool
no setter
log → Logger
final
logging bool
getter/setter pair
registry Map<SpotKey, SpotService>
Registry of all types => dependencies Uses SpotKey to support both unnamed and named instances
final

Static Methods

createScope() SpotContainer
Create a scoped container that inherits from the global Spot registry.
dispose<T>({String? name}) → void
Disposes a specific singleton instance
disposeAll() → void
Disposes all registered services
getRegistered<T>({String? name}) SpotService<T>
init(void initializer(void factory<T, R extends T>(SpotGetter<R> locator, {String? name}), void single<T, R extends T>(SpotGetter<R> locator, {String? name}))) → void
Convenience method for registering dependencies Alternatively, you can just call Spot.registerFactory & Spot.registerSingle directly
isRegistered<T>({String? name}) bool
Check if a type is registered without throwing an exception Useful for conditional logic and debugging
printRegistry() → void
Print all registered types with their details to the log Useful for debugging and inspecting the DI container state
registerAsync<T, R extends T>(SpotAsyncGetter<R> locator, {String? name}) → void
Registers an async singleton with asynchronous initialization.
registerFactory<T, R extends T>(SpotGetter<R> locator, {String? name}) → void
Registers a factory that creates a new instance on each resolution.
registerSingle<T, R extends T>(SpotGetter<R> locator, {String? name}) → void
Registers a singleton that returns the same instance on each resolution.
spot<T>({String? name}) → T
Resolves and returns an instance of type T.
spotAsync<T>({String? name}) Future<T>
Resolves and returns an async singleton of type T.