Tutorials
Scope a Flutter application
Attach an application container, create a feature Scope, and resolve dependencies through BuildContext.
Scope a Flutter application
This tutorial exposes one application container through the widget tree and creates a shorter-lived feature container below it. The application owns shared infrastructure; the feature Scope owns only feature-specific objects.
1. Install both packages
flutter pub add ark_di:^1.1.0 ark_di_flutter:^1.1.0
Add ark_di directly when application code imports DiContainer, DiBinder, or another core type.
2. Build the application graph before runApp
void main() {
final application = DiContainer.build((binder) {
binder.bindInstance<GreetingRepository>(
const GreetingRepository(),
);
});
runApp(
DiRootScope(
container: application,
child: const Application(),
),
);
}
DiRootScope exposes an existing container; it does not create registrations during build. Decide whether the Scope or the bootstrap owner closes the container through DiScopeOwnership.
3. Create a feature Scope
The feature adds one local value while inheriting the Repository from its parent:
final class GreetingFeature extends StatelessWidget {
const GreetingFeature({super.key});
@override
Widget build(BuildContext context) => DiScope(
configure: (binder) {
binder.bindInstance<FeatureName>(
const FeatureName('Ark DI Flutter'),
);
},
child: const GreetingView(),
);
}
configure is a one-shot construction callback. When this widget owns a Presenter, controller, or feature service that requires disposal, bind it with a disposer in this child Scope.
4. Resolve through the current BuildContext
final class GreetingView extends StatelessWidget {
const GreetingView({super.key});
@override
Widget build(BuildContext context) {
final repository = context.di.get<GreetingRepository>();
final feature = context.di.get<FeatureName>();
return Text(repository.greetingFor(feature.value));
}
}
context.di subscribes to Scope identity and is appropriate in build or didChangeDependencies. If an ancestor replaces the container, the widget rebuilds against the new Scope. context.readDi performs a one-off lookup for a callback and does not establish that dependency.
5. Understand replacement and teardown
When a DiScope is removed, it closes its child container and container-owned dependencies. The parent remains open. If the nearest parent container changes, DiScope creates a new child under the new parent and closes the old child.
Do not retain context.di or a resolved feature object in a longer-lived global variable. Its lifetime is bounded by the Scope that provided it.
What belongs at each level
| Application Scope | Feature Scope | Widget State |
|---|---|---|
| API clients, shared repositories, application services | Feature Presenter, coordinator, feature-local service | Animation and text controllers, focus nodes, render-local state |
This division is a design choice rather than a package restriction. The important rule is that a shorter-lived object never escapes into a longer-lived owner.