You are configuring dependency injection for a Flutter feature using flutter_injections.
DI Architecture Overview
FlutterInjectionsWidget (root, in AppWidget)
└── CoreInjections.core() ← global singletons (Dio, repos, session)
├── auth()
├── services()
├── eventBus()
└── ...
FlutterModule (per-module, lazy)
└── HomeModule.injections ← scoped to module lifetime
├── HomeController
└── HomeConsumer
Registration Lifetimes
| Type | Usage | Pattern |
|---|---|---|
singleton |
One instance for app lifetime | Dio, Session, Broker |
lazySingleton |
Created on first use, then kept | Controllers, Repositories |
factory |
New instance every get<T>() call |
Usecases, one-time actions |
Step 1: Register in CoreInjections
File: core/lib/injections.dart
Add a new static method for the domain group:
class CoreInjections {
static List<Inject<Object>> core() => [
...auth(),
...services(),
...eventBus(),
...<module_name>(), // ← ADD THIS
];
// ← ADD THIS METHOD
static List<Inject<Object>> <module_name>() => [
Inject<<ModuleName>Datasource>(
(i) => <ModuleName>Datasource(i.find<Dio>()),
),
Inject<<ModuleName>Repository>(
(i) => <ModuleName>RepositoryImpl(i.find<<ModuleName>Datasource>()),
),
];
}
Step 2: Register controller in the Module
File: modules/<module_name>/<module_name>_module.dart
Extend FlutterModule and override its two getters, injections and
child. FlutterModule is a StatelessWidget that wraps its child in a
FlutterInjectionsWidget for you — don't build that wrapper by hand.
class <ModuleName>Module extends FlutterModule {
const <ModuleName>Module({super.key});
@override
List<Inject<Object>> get injections => [
Inject<<ModuleName>Controller>.lazySingleton(
(i) => <ModuleName>Controller(
Get<ModuleName>Usecase(i.find<<ModuleName>Repository>()),
),
),
];
@override
Widget get child => BlocProvider(
create: (_) => FlutterInjections.get<<ModuleName>Controller>(),
child: const <ModuleName>Screen(),
);
}
Both getters are @override — they are abstract on FlutterModule, so
omitting either is a compile error rather than a silent no-op.
Step 3: Resolve dependencies
In widgets/screens (via BlocProvider):
context.read<<ModuleName>Controller>()
Programmatic resolution (outside widget tree):
final controller = FlutterInjections.get<<ModuleName>Controller>();
Inside an Inject factory (chain resolution):
Inject<OrderController>.lazySingleton(
(i) => OrderController(
getOrders: i.find<GetOrdersUsecase>(),
broker: i.find<Broker>(),
),
),
Patterns to Follow
Usecase as factory (stateless, safe to create per-use)
Inject<Get<ModuleName>Usecase>(
(i) => Get<ModuleName>Usecase(i.find<<ModuleName>Repository>()),
),
Controller as lazySingleton (stateful, one per scope)
Inject<<ModuleName>Controller>.lazySingleton(
(i) => <ModuleName>Controller(i.find<Get<ModuleName>Usecase>()),
),
Repository as singleton (shared data layer)
Inject<<ModuleName>Repository>(
(i) => <ModuleName>RepositoryImpl(i.find<<ModuleName>Datasource>()),
),
Event Bus DI
When a module needs to publish/subscribe events via Broker:
Inject<PaymentConsumer>(
(i) => PaymentConsumer(broker: i.find<Broker>()),
),
Common Mistakes to Avoid
- Do NOT register controllers in
CoreInjections— they belong in the module scope - Do NOT register usecases as singletons — they are stateless and should be factories
- Always use
i.find<T>()inside Inject factory, neverFlutterInjections.get<T>()(which bypasses scope) - Order matters — list dependencies before dependents in the injections list
Instructions
- Read
core/lib/injections.dartto understand existing registrations - Find the right scope: global (CoreInjections) vs module-scoped (Module.injections)
- Apply the correct lifetime: singleton/lazySingleton/factory
- Register datasource → repository in Core; usecase + controller in Module
- Check for circular dependencies before registering