Learn
Getting started
Configure, resolve, and close a small Ark DI container.
SDK security baseline
Build and distribute applications with Dart 3.13.7+ or Flutter 3.47.8+. These are the first patched stable versions for the eMudhra incident; for later builds, use a currently supported SDK with security fixes.
The pubspec minimum describes API compatibility, not the security of an older runtime. Packages were validated with Dart 3.13.7 and Flutter 3.47.8 without changing their public API. Updating a library does not repair an installed application: affected Android, Windows, and Linux applications must be rebuilt and redistributed.
Check the SDK selected by the IDE, local builds, and CI. Updating a global SDK does not ensure that a particular project or CI uses it. Do not disable certificate verification to work around TLS errors.
This page builds a small object graph with three different lifetimes. The
important result is not merely obtaining CatalogService: it is knowing which
scope creates, retains, and closes every object involved.
1. Add the package
dart pub add ark_di
import 'package:ark_di/ark_di.dart';
2. Keep application objects unaware of the container
Dependencies remain ordinary constructor parameters:
final class AppConfig {
const AppConfig(this.apiBaseUrl);
final Uri apiBaseUrl;
}
final class ApiClient {
ApiClient(this.config);
final AppConfig config;
Future<void> close() async {}
}
final class CatalogService {
CatalogService(this.client);
final ApiClient client;
}
None of these classes imports Ark DI or asks a global service locator for its dependencies. Only the application composition root knows how the graph is built.
3. Configure the graph once
final AppConfig config = AppConfig(Uri.parse('https://api.example.com'));
final DiContainer container = DiContainer.build((binder) {
binder.bindInstance<AppConfig>(config);
binder.bindLazySingleton<ApiClient>(
(resolver) => ApiClient(resolver.get<AppConfig>()),
dispose: (client) => client.close(),
);
binder.bindFactory<CatalogService>(
(resolver) => CatalogService(resolver.get<ApiClient>()),
);
});
Each registration expresses a different lifecycle:
| Registration | What happens |
|---|---|
bindInstance | Stores the supplied AppConfig; the container owns the registration by default. |
bindLazySingleton | Creates one ApiClient on first resolution, caches it, and invokes its disposer during close. |
bindFactory | Creates a new CatalogService for every resolution and does not retain it. |
The configuration callback is one-shot. DiBinder expires when it returns, so
runtime code cannot silently mutate registrations.
4. Resolve from the composition boundary
final CatalogService first = container.get<CatalogService>();
final CatalogService second = container.get<CatalogService>();
assert(!identical(first, second));
assert(identical(first.client, second.client));
The services are transient, while both receive the same container-owned
client. Application code can now pass first to the feature that needs it
through an ordinary constructor.
5. Close the owner
await container.close();
Closing the container waits for active resolutions, closes child scopes, and disposes owned cached objects in dependency order. It does not dispose factory results because it never retained them; the consumer owns any resources held by a transient result.
Next, read the composition-root guide before introducing child scopes or asynchronous registrations.