ark_di 0.1.0-dev.1 copy "ark_di: ^0.1.0-dev.1" to clipboard
ark_di: ^0.1.0-dev.1 copied to clipboard

Pure Dart dependency injection with hierarchical scopes and explicit ownership.

example/ark_di_example.dart

import 'package:ark_di/ark_di.dart';

Future<void> main() async {
  final List<String> lifecycle = <String>[];

  final DiContainer application = DiContainer.build((binder) {
    binder.bindInstance<AppConfig>(
      const AppConfig(apiBaseUrl: 'https://api.example.com'),
    );

    binder.bindAsyncLazySingleton<ApiClient>(
      (resolver) async {
        final AppConfig config = resolver.get<AppConfig>();
        return ApiClient.connect(config.apiBaseUrl);
      },
      dispose: (client) {
        client.close();
        lifecycle.add('api-client');
      },
    );

    binder.bindAsyncLazySingleton<CatalogRepository>(
      (resolver) async {
        final ApiClient client = await resolver.getAsync<ApiClient>();
        return RemoteCatalogRepository(client);
      },
      dispose: (repository) {
        repository.close();
        lifecycle.add('repository');
      },
    );

    binder.bindAsyncLazySingleton<CatalogService>(
      (resolver) async {
        final CatalogRepository repository = await resolver
            .getAsync<CatalogRepository>();
        return CatalogService(repository);
      },
      dispose: (service) {
        service.close();
        lifecycle.add('service');
      },
    );
  });

  final CatalogService production = await application
      .getAsync<CatalogService>();
  final List<String> remoteBooks = await production.search('dart');

  if (remoteBooks.single != 'Remote result for "dart"') {
    throw StateError('The production graph returned an unexpected result.');
  }

  final DiContainer preview = application.createChild((binder) {
    binder.bindInstance<CatalogRepository>(
      MemoryCatalogRepository(<String>['Ark DI guide', 'UseCase Forge guide']),
      overrideParent: true,
      dispose: (repository) {
        repository.close();
        lifecycle.add('preview-repository');
      },
    );

    binder.bindLazySingleton<CatalogService>(
      (resolver) => CatalogService(resolver.get<CatalogRepository>()),
      overrideParent: true,
      dispose: (service) {
        service.close();
        lifecycle.add('preview-service');
      },
    );
  });

  final CatalogService previewService = preview.get<CatalogService>();
  final List<String> previewBooks = await previewService.search('guide');

  if (previewBooks.length != 2) {
    throw StateError('The child scope did not use its local repository.');
  }

  await preview.close();
  await application.close();

  const List<String> expectedLifecycle = <String>[
    'preview-service',
    'preview-repository',
    'service',
    'repository',
    'api-client',
  ];

  if (!_sameItems(lifecycle, expectedLifecycle)) {
    throw StateError(
      'Unexpected teardown order: $lifecycle. Expected: $expectedLifecycle.',
    );
  }

  print('Production: $remoteBooks');
  print('Preview: $previewBooks');
  print('Teardown: $lifecycle');
}

bool _sameItems(final List<String> left, final List<String> right) {
  if (left.length != right.length) {
    return false;
  }
  for (var index = 0; index < left.length; index += 1) {
    if (left[index] != right[index]) {
      return false;
    }
  }
  return true;
}

final class AppConfig {
  const AppConfig({required this.apiBaseUrl});

  final String apiBaseUrl;
}

final class ApiClient {
  ApiClient._(this.baseUrl);

  final String baseUrl;
  bool _closed = false;

  static Future<ApiClient> connect(final String baseUrl) async {
    await Future<void>.delayed(Duration.zero);
    return ApiClient._(baseUrl);
  }

  Future<List<String>> search(final String query) async {
    if (_closed) {
      throw StateError('ApiClient is closed.');
    }
    return <String>['Remote result for "$query"'];
  }

  void close() {
    _closed = true;
  }
}

abstract interface class CatalogRepository {
  Future<List<String>> search(String query);

  void close();
}

final class RemoteCatalogRepository implements CatalogRepository {
  RemoteCatalogRepository(this._client);

  final ApiClient _client;
  bool _closed = false;

  @override
  Future<List<String>> search(final String query) {
    if (_closed) {
      throw StateError('RemoteCatalogRepository is closed.');
    }
    return _client.search(query);
  }

  @override
  void close() {
    _closed = true;
  }
}

final class MemoryCatalogRepository implements CatalogRepository {
  MemoryCatalogRepository(this._items);

  final List<String> _items;
  bool _closed = false;

  @override
  Future<List<String>> search(final String query) async {
    if (_closed) {
      throw StateError('MemoryCatalogRepository is closed.');
    }
    final String normalized = query.toLowerCase();
    return _items
        .where((item) => item.toLowerCase().contains(normalized))
        .toList(growable: false);
  }

  @override
  void close() {
    _closed = true;
  }
}

final class CatalogService {
  CatalogService(this._repository);

  final CatalogRepository _repository;
  bool _closed = false;

  Future<List<String>> search(final String query) {
    if (_closed) {
      throw StateError('CatalogService is closed.');
    }
    return _repository.search(query);
  }

  void close() {
    _closed = true;
  }
}
0
likes
160
points
--
downloads

Documentation

Documentation
API reference

Publisher

verified publisherarktelos.dev

Weekly Downloads

Pure Dart dependency injection with hierarchical scopes and explicit ownership.

Homepage
Repository (GitLab)
View/report issues

Topics

#architecture #dependency-injection #inversion-of-control

License

Apache-2.0 (license)

More

Packages that depend on ark_di