leulit_flutter_dependency_injection
A dependency-injection facade over get_it. Your call sites depend on DI/di, never on the engine — swap the engine without touching them.
Positioning. This is a composition-root wiring utility, not your app's DI strategy. Views and ViewModels still receive dependencies via constructors. See
doc/adr/0001-service-locator-facade-over-get-it.md.
Features
- Static facade
DI+ globaldishortcut (+DI.instancefor the internal singleton). - Eager / lazy / async singletons, factories and param factories.
- Named instances and scopes (
pushScope/popScope/dropScope) with automatic disposal. - Fail-fast resolution
get<T>()with typed exceptions; null-safetryGet<T>(). Disposable.onDispose()invoked automatically onunregister/popScope/reset.- Engine isolated behind the
DependencyContainerabstraction (DIP). get_it is never re-exported.
Installation
dependencies:
leulit_flutter_dependency_injection: ^0.1.0
Basic usage
import 'package:leulit_flutter_dependency_injection/leulit_flutter_dependency_injection.dart';
// Composition root
DI.registerSingleton<AuthStore>(AuthStore());
DI.registerLazySingleton<UserRepo>(() => UserRepo(DI.get<ApiClient>()));
// Anywhere
final repo = di.get<UserRepo>(); // fail-fast
final maybe = di.tryGet<UserRepo>(); // null if absent
Advanced
Named instances
DI.registerSingleton<ApiClient>(ApiClient('prod'), instanceName: 'prod');
DI.registerSingleton<ApiClient>(ApiClient('staging'), instanceName: 'staging');
final prod = di.get<ApiClient>(instanceName: 'prod');
Scopes
DI.pushScope(scopeName: 'session', dispose: () => print('session closed'));
DI.registerSingleton<SessionStore>(SessionStore());
// ...
await DI.popScope(); // disposes the scope's registrations
Async singletons
DI.registerSingletonAsync<Db>(() async => Db.open());
await DI.allReady(); // or DI.ready()
final db = di.get<Db>();
Factories
DI.registerFactory<Uuid>(() => Uuid()); // new instance per get
DI.registerFactoryParam<Greeting, String, void>((name, _) => Greeting(name));
final g = di.get<Greeting>(param1: 'Ada');
Disposal
Types implementing Disposable get onDispose() called automatically on unregister,
popScope and reset. Alternatively, pass an explicit dispose: callback to any
register* call.
class Db implements Disposable {
@override
Future<void> onDispose() => _connection.close();
}
Error model
get<T>() throws DependencyNotRegisteredException when the type is not registered; tryGet<T>() returns null. Double registration throws DependencyAlreadyRegisteredException.
Testing
setUp(DI.resetContainerOverride); // fresh isolated container per test
tearDown(() => DI.reset());
Inject a custom container (e.g. a fake) with DI.overrideContainer(myContainer).
Why a facade
Your call sites depend on DI/di, not on get_it. Changing the engine means writing a
new DependencyContainer adapter; the rest of the app is untouched.
Architecture
call sites ──► DI (static) + di (const global)
│
▼
DependencyContainer (abstraction / DIP)
│
▼
GetItContainer (only file that touches get_it)
│
▼
GetIt.asNewInstance()
License
MIT © 2026 Leulit.
Contributing
Issues and PRs welcome. Run flutter analyze, dart format . and flutter test before
submitting.