spot_di 1.0.1 copy "spot_di: ^1.0.1" to clipboard
spot_di: ^1.0.1 copied to clipboard

A lightweight and powerful service locator for Dart and Flutter, supporting singletons, factories, async initialization, and scopes with zero dependencies.

Spot #

Spot is a lightweight dependency injection (DI) framework for Dart and Flutter applications. Technically implemented as a service locator pattern, Spot provides a minimal yet powerful API for managing dependencies with support for singletons, factories, async initialization, and scoped containers.

Spot is currently in early development. The API may change in future releases.

Features #

  • ๐ŸŽฏ Simple API - Register and resolve dependencies with minimal boilerplate
  • ๐Ÿญ Factory & Singleton Support - Choose the right lifecycle for each dependency
  • โšก Async Initialization - Handle services that require async setup
  • ๐Ÿ” Named Instances - Register multiple implementations of the same type
  • ๐Ÿงช Scoped Containers - Isolate dependencies for testing or feature modules
  • โ™ป๏ธ Lifecycle Management - Automatic cleanup via SpotDisposable interface
  • ๐Ÿ”’ Type Safety - Full compile-time type checking with generics
  • ๐Ÿšซ Circular Dependency Detection - Clear error messages when things go wrong
  • ๐Ÿ“ฆ Zero Dependencies - No external runtime dependencies

Installation #

Add this to your pubspec.yaml:

dependencies:
  spot_di: ^1.0.0

Then run:

dart pub get

Quick Start #

Basic Usage #

import 'package:spot_di/spot.dart';

// 1. Define your interfaces and implementations
abstract class ILogger {
  void log(String message);
}

class ConsoleLogger implements ILogger {
  @override
  void log(String message) => print('[LOG] $message');
}

// 2. Register dependencies
void main() {
  Spot.registerSingle<ILogger, ConsoleLogger>((get) => ConsoleLogger());
  
  // 3. Resolve and use
  final logger = spot<ILogger>();
  logger.log('Hello, Spot!');
}

Registration Patterns #

Singleton - One instance shared across the app:

Spot.registerSingle<ISettings, AppSettings>((get) => AppSettings());

final settings1 = spot<ISettings>();
final settings2 = spot<ISettings>();
print(identical(settings1, settings2)); // true

Factory - New instance on each resolution:

Spot.registerFactory<IRepository, UserRepository>(
  (get) => UserRepository(),
);

final repo1 = spot<IRepository>();
final repo2 = spot<IRepository>();
print(identical(repo1, repo2)); // false

Async Singleton - For services requiring async initialization:

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

// Must use spotAsync for async singletons
final db = await spotAsync<Database>();

Dependency Injection #

Services can depend on other services using the get parameter:

// Register dependencies in order
Spot.registerSingle<ILogger, ConsoleLogger>((get) => ConsoleLogger());

Spot.registerSingle<IApiClient, ApiClient>((get) {
  return ApiClient(
    logger: get<ILogger>(),  // Inject dependencies
  );
});

// Spot handles the dependency graph
final apiClient = spot<IApiClient>();

Bulk Registration #

Use the init helper for cleaner registration:

Spot.init((factory, single) {
  // Register singletons
  single<ILogger, ConsoleLogger>((get) => ConsoleLogger());
  single<ISettings, AppSettings>((get) => AppSettings());
  
  // Register factories
  factory<IRepository, UserRepository>(
    (get) => UserRepository(get<ILogger>()),
  );
});

Named Instances #

Register multiple implementations of the same type:

Spot.registerSingle<HttpClient, PublicHttpClient>(
  (get) => PublicHttpClient(),
  name: 'public',
);

Spot.registerSingle<HttpClient, AuthenticatedHttpClient>(
  (get) => AuthenticatedHttpClient(),
  name: 'authenticated',
);

// Resolve by name
final publicClient = spot<HttpClient>(name: 'public');
final authClient = spot<HttpClient>(name: 'authenticated');

Lifecycle Management #

Implement SpotDisposable for automatic cleanup:

class DatabaseService implements SpotDisposable {
  late Database _db;
  
  @override
  void dispose() {
    _db.close();
  }
}

Spot.registerSingle<DatabaseService, DatabaseService>(
  (get) => DatabaseService(),
);

// Cleanup specific service
Spot.dispose<DatabaseService>();  // Calls dispose() automatically

// Or cleanup everything on app shutdown
Spot.disposeAll();

Scoped Containers #

Create isolated dependency scopes for testing or feature modules:

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

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

// Each scope has its own version
final prodSettings = spot<ISettings>();        // Gets AppSettings
final testSettings = testScope.spot<ISettings>();  // Gets MockSettings

// Cleanup test scope (doesn't affect global)
testScope.dispose();

Nested Scopes #

Scopes can inherit from parent scopes:

final parentScope = Spot.createScope();
final childScope = parentScope.createChild();

// Child falls back to parent for missing dependencies
parentScope.registerSingle<ILogger, ConsoleLogger>(
  (get) => ConsoleLogger(),
);

final logger = childScope.spot<ILogger>();  // Gets from parent

Advanced Usage #

Checking Registration #

if (Spot.isRegistered<ILogger>()) {
  print('Logger is registered');
}

if (Spot.isRegistered<HttpClient>(name: 'public')) {
  print('Public HTTP client is registered');
}

Debugging #

// Enable verbose logging
Spot.logging = true;

// Print all registered types
Spot.printRegistry();
// Output:
// === Spot Registry (3 types) ===
//   ILogger -> ConsoleLogger [singleton] (initialized)
//   ISettings -> AppSettings [singleton]
//   IRepository -> UserRepository [factory]
// ==================================================

Error Handling #

Spot provides clear error messages:

// Unregistered type
try {
  final service = spot<UnregisteredService>();
} catch (e) {
  // SpotException: Type UnregisteredService is not registered in Spot container.
  // Registered types: ILogger, ISettings
}

// Circular dependency
Spot.registerSingle<ServiceA, ServiceA>(
  (get) => ServiceA(get<ServiceB>()),
);
Spot.registerSingle<ServiceB, ServiceB>(
  (get) => ServiceB(get<ServiceA>()),
);

try {
  spot<ServiceA>();
} catch (e) {
  // SpotException: Circular dependency detected: ServiceA -> ServiceB -> ServiceA
}

Testing #

Spot makes testing easy with scoped containers:

import 'package:test/test.dart';
import 'package:spot/spot.dart';

void main() {
  // Setup production dependencies once
  setUpAll(() {
    Spot.registerSingle<IApiClient, ApiClient>((get) => ApiClient());
  });
  
  // Clean up after each test
  tearDown(() {
    Spot.disposeAll();
  });
  
  test('with mocked dependencies', () {
    // Create isolated test scope
    final testScope = Spot.createScope();
    testScope.registerSingle<IApiClient, MockApiClient>(
      (get) => MockApiClient(),
    );
    
    // Test with mock
    final apiClient = testScope.spot<IApiClient>();
    expect(apiClient, isA<MockApiClient>());
    
    // Cleanup
    testScope.dispose();
  });
}

Service Locator Pattern #

Spot is technically a service locator pattern rather than pure dependency injection. This means:

What it is:

  • A centralized registry where services can be registered and retrieved
  • Dependencies are resolved at runtime using spot<T>()
  • Simple, straightforward, and easy to understand

What it's not:

  • Not constructor injection (dependencies aren't automatically injected)
  • Not compile-time dependency resolution
  • Not a full-featured DI container like Angular's injector

Why this is fine for Dart/Flutter:

  • Dart doesn't have built-in reflection for constructor injection
  • Service locator pattern is lightweight and performant
  • Most Flutter apps use similar patterns (GetIt, Provider, etc.)
  • You get the benefits of DI (loose coupling, testability) with minimal overhead

API Reference #

Registration Methods #

Method Description
registerSingle<T, R>(locator) Register a singleton (lazy initialization)
registerFactory<T, R>(locator) Register a factory (new instance each time)
registerAsync<T, R>(locator) Register async singleton
init(initializer) Bulk registration helper

Resolution Methods #

Method Description
spot<T>({name}) Resolve dependency synchronously
spotAsync<T>({name}) Resolve async singleton
Spot.spot<T>({name}) Static method (same as global spot)
Spot.spotAsync<T>({name}) Static method (same as global spotAsync)

Lifecycle Methods #

Method Description
dispose<T>({name}) Dispose specific service
disposeAll() Dispose all services

Utility Methods #

Method Description
isRegistered<T>({name}) Check if type is registered
printRegistry() Print all registered types (debugging)
createScope() Create scoped container

Container Methods #

Method Description
container.registerSingle<T, R>(locator) Register in scope
container.registerFactory<T, R>(locator) Register factory in scope
container.registerAsync<T, R>(locator) Register async in scope
container.spot<T>({name}) Resolve from scope
container.spotAsync<T>({name}) Resolve async from scope
container.createChild() Create nested scope
container.dispose() Dispose scope

Examples #

See the example directory for complete examples:

Development #

Git Hooks #

This repository includes Git hooks to ensure code quality:

  • pre-push: Runs dart analyze and dart test before pushing any branch

To install the hooks after cloning:

./hooks/install.sh

The hooks will automatically prevent pushes if analysis or tests fail.

Contributing #

Contributions are welcome! Please feel free to submit issues or pull requests.

Before submitting a PR, make sure to:

  1. Install the Git hooks: ./hooks/install.sh
  2. Run dart analyze to check for issues
  3. Run dart test to ensure all tests pass
  4. Run dart format . to format your code

License #

This project is licensed under the MIT License - see the LICENSE file for details.

1
likes
140
points
176
downloads

Publisher

unverified uploader

Weekly Downloads

A lightweight and powerful service locator for Dart and Flutter, supporting singletons, factories, async initialization, and scopes with zero dependencies.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

More

Packages that depend on spot_di