inject_x 0.0.6
inject_x: ^0.0.6 copied to clipboard
A minimalistic Dependency Injestion solution similar to the Angular one
InjectX #
A lightweight, easy-to-use service locator implementation for Dart applications. This package provides a simple dependency injection container that helps manage application dependencies with minimal setup.
Features #
- Simple registration and retrieval of dependencies
- Singleton instance management
- Automatic disposal of services
- Type-safe dependency injection
- Angular-style
injectfunction - Lazy and async factory registration
- Circular dependency detection
- Zero external dependencies
Installation #
Add this to your package's pubspec.yaml file:
dependencies:
inject_x: ^0.0.6
Usage #
Basic Usage #
// Register your dependencies
InjectX.add<UserService>(UserService());
InjectX.add<AuthService>(AuthService());
// Retrieve instances
final userService = InjectX.get<UserService>();
final authService = InjectX.get<AuthService>();
// Alternatively, use Angular-style injection
final userService = inject<UserService>();
Init Method #
If your class defines an init() method, it will be called automatically before the first injection. This is useful when your service depends on other services that need to be resolved after construction — particularly in cases of circular dependencies.
class UserService {
late AuthService authService;
void init() {
authService = inject<AuthService>();
}
}
The init() method is called only once, just before the first access via get() or inject(). If init() throws, the error propagates to the caller and subsequent calls will retry init().
Async Init Method #
If your class has an async init() method (returning a Future), you can use injectAsync<T>() or InjectX.getAsync<T>() to ensure initialization is awaited before use.
class ConfigService {
String? config;
Future<void> init() async {
await Future.delayed(Duration(milliseconds: 100));
config = 'loaded';
}
}
void main() async {
InjectX.add(ConfigService());
final config = await injectAsync<ConfigService>();
}
Automatic Disposal #
InjectX automatically calls a service's dispose() method if it exists when the service is removed:
class DatabaseService {
void dispose() {
// Cleanup resources
}
}
// Register the service
InjectX.add<DatabaseService>(DatabaseService());
// Later, remove it
InjectX.remove<DatabaseService>(); // dispose() will be called automatically
Lazy Registration #
Create the instance only when it's first requested:
InjectX.registerLazy<ExpensiveService>(() => ExpensiveService());
// Instance is created here, not at registration time
final service = InjectX.get<ExpensiveService>();
Async Registration #
For services that require async creation (e.g., loading config, connecting to a database):
InjectX.registerAsync<DatabaseService>(() async {
final db = DatabaseService();
await db.connect();
return db;
});
// Must use getAsync/injectAsync — sync get throws StateError
final db = await injectAsync<DatabaseService>();
Circular Dependency Detection #
InjectX detects circular get() calls and throws a clear StateError instead of hanging or overflowing the stack:
class A {
void init() { inject<B>(); }
}
class B {
void init() { inject<A>(); } // Throws: Circular dependency detected for type A
}
Use the init() pattern to resolve circular dependencies — init() is called after construction, allowing dependencies to be resolved lazily.
API Reference #
Methods #
add<T>(T instance): Register an existing instanceregisterLazy<T>(T Function() factory): Register a factory; instance created on firstgetregisterAsync<T>(Future<T> Function() factory): Register an async factory; usegetAsyncto retrievecontains<T>(): Check if a type is registered (instance, lazy, or async)get<T>(): Retrieve a registered instance; callsinit()if defined (throws ifinit()fails)getAsync<T>(): Retrieve a registered instance; awaitsinit()if it's async (throws ifinit()fails)remove<T>(): Remove a registered dependency and calldispose()if definedlength: The number of registered dependenciesclear(): Remove all dependencies and dispose each (if supported)
Helper Functions #
inject<T>(): Angular-style helper to callInjectX.get<T>()injectAsync<T>(): Angular-style helper for services with asyncinit()
Error Handling #
// Check if a type is registered before accessing it
if (InjectX.contains<MyService>()) {
final service = InjectX.get<MyService>();
}
// Attempting to retrieve a non-existent dependency
try {
final service = InjectX.get<UnregisteredService>();
} catch (e) {
// Throws StateError: No instance registered for type UnregisteredService
}
If an init() method throws, the error propagates to the caller of get<T>() or getAsync<T>().
Best Practices #
- Register all dependencies early in your app lifecycle
- Use meaningful type parameters for clarity
- Implement
dispose()for services that require cleanup - Remove services when no longer needed (e.g., on module unload)
Example #
class UserService {
void dispose() {
// Cleanup
}
}
class AuthService {
final UserService userService;
AuthService(this.userService);
}
void main() {
// Register services
InjectX.add<UserService>(UserService());
// Register a dependent service
InjectX.add<AuthService>(AuthService(inject<UserService>()));
// Use services
final userService = inject<UserService>();
final authService = inject<AuthService>();
// Cleanup
InjectX.remove<AuthService>();
InjectX.remove<UserService>();
}
Contributing #
Contributions are welcome! Please feel free to submit a pull request.
For major changes, open an issue first to discuss what you'd like to propose.
License #
This project is licensed under the MIT License — see the LICENSE file for details.