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 + global di shortcut (+ DI.instance for 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-safe tryGet<T>().
  • Disposable.onDispose() invoked automatically on unregister / popScope / reset.
  • Engine isolated behind the DependencyContainer abstraction (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.