Ark DI Flutter

Flutter scope and BuildContext integration for Ark DI.

Ark DI Flutter connects an existing DiContainer hierarchy to the widget tree. It owns no global container, performs no reflection, and does not turn dependency injection into state management. Application objects continue to receive dependencies through constructors; widgets use the container at explicit composition boundaries.

Русская версия

0.1.0-dev.1 is a prerelease. The lifecycle model is implemented and tested, but public API details may change before the first stable version.

Installation

dependencies:
  ark_di: ^0.1.0-dev.1
  ark_di_flutter: ^0.1.0-dev.1
import 'package:ark_di/ark_di.dart';
import 'package:ark_di_flutter/ark_di_flutter.dart';

Root scope

Build the application container at the Composition Root and expose it above the application:

final DiContainer application = DiContainer.build((binder) {
  binder.bindLazySingleton<ApiClient>(
    (resolver) => ApiClient(),
    dispose: (client) => client.close(),
  );
});

runApp(
  DiRootScope(
    container: application,
    child: const Application(),
  ),
);

DiRootScope owns its container by default and starts close() when the widget stops exposing it. Use DiScopeOwnership.external when another object controls the root lifecycle.

Because Flutter disposal is synchronous while DiContainer.close() is asynchronous, closing continues without blocking the widget tree. Supply onCloseError to receive teardown failures. Without a handler, failures are reported through the Zone active when closing starts.

Child scope

DiScope creates and owns a real child container. Missing dependencies fall back through the Ark DI parent hierarchy:

DiScope(
  configure: (binder) {
    binder.bindInstance<FeatureId>(featureId);
    binder.bindLazySingleton<FeaturePresenter>(
      (resolver) => FeaturePresenter(
        repository: resolver.get<UserRepository>(),
        featureId: resolver.get<FeatureId>(),
      ),
      dispose: (presenter) => presenter.close(),
    );
  },
  child: const FeatureScreen(),
)

The configuration callback is required and runs once for each parent-container identity. Ark DI registrations are immutable after creation. Give DiScope a new Key when changed configuration must create a fresh scope.

BuildContext access

final UserRepository repository = context.readDi.get<UserRepository>();
final Session session = await context.readDi.getAsync<Session>();

The extension exposes four accessors:

Accessor Subscribes to scope replacement Missing scope
context.di Yes Throws DiScopeNotFoundException
context.maybeDi Yes Returns null
context.readDi No Throws DiScopeNotFoundException
context.maybeReadDi No Returns null

Use di from build or didChangeDependencies when the widget must follow a replacement scope. Use readDi for one-off event-handler access. Do not cache a container retrieved from BuildContext for later lifecycle callbacks.

Opt-in Service Locator

The main library contains no Service Locator. Compatibility for applications that explicitly require one is isolated behind another import:

import 'package:ark_di_flutter/service_locator.dart';

abstract final class ApplicationDependencies {
  static final DiServiceLocator locator = DiServiceLocator();
}

DiServiceLocator is an ordinary object. The package creates no global or static instance. attach and detach are explicit, and the locator never owns or closes the attached container.

Scope boundaries

Ark DI Flutter handles widget-tree placement, child-scope lifetime, context lookup, and close-error delivery. It does not manage UI state, rebuild widgets when dependency data changes, create Presenters automatically, or discover dispose() methods by convention.

See the runnable example and the Ark DI architecture guide.

License

Apache License 2.0. See LICENSE and NOTICE.

Libraries

ark_di_flutter
Flutter scope integration for Ark DI dependency containers.
service_locator
Opt-in Service Locator compatibility for Ark DI applications.