Tutorials

Scope a Flutter application

Attach an application container, create a feature Scope, and resolve dependencies through BuildContext.

v1.1.0Intermediateark_di_flutter

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

shell
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

dart
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:

dart
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

dart
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 ScopeFeature ScopeWidget State
API clients, shared repositories, application servicesFeature Presenter, coordinator, feature-local serviceAnimation 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.