Compare commits

..

2 Commits
1.0.3 ... 0.0.2

Author SHA1 Message Date
Sergey Penkovsky
1aa0ae045e Добавить .gitlab-ci.yml 2021-04-14 17:19:35 +03:00
Sergey Penkovsky
12d877333a upgraded code for nullsafety 2021-03-27 19:48:03 +03:00
30 changed files with 1036 additions and 1629 deletions

View File

@@ -1 +0,0 @@
Sergey Penkovsky <sergey.penkovsky@gmail.com>

View File

@@ -1,38 +0,0 @@
# Changelog
1.0.3 Added provider with params
---
1.0.2 Updated doc and fixed syntax error
---
1.0.1 Fixed syntax error
---
1.0.0 Refactored code and added experimental api
---
0.1.2+1 Fixed initializtaion error
---
0.1.2 Fixed warnings in code
---
0.1.1+2 Updated libraries and fixed warnings
---
0.1.1+1 Updated pubspec and readme.md
---
0.1.1 Updated pubspec
---
0.1.0 Initial release
---

282
README.md
View File

@@ -1,135 +1,196 @@
# Quick start
# dart_di
## Main components DI
Экспериментальная разработка DI на ЯП Dart
## Документация
### Binding
### Быстрый старт
Binding is a custom instance configurator that contains methods for configuring a dependency.
Основным классом для всех операций является `DiContainer`. Вы можете зарегистрировать свои зависимости,
получив `ResolvingContext` через метод `bind<T>()` и используя его различные методы разрешений.
Далее вы можете получить зависимости с помощью `resolve<T>()`.
There are two main methods for initializing a custom instance `toInstance()` and `toProvide()` and auxiliary `withName()` and `singleton()`.
`toInstance()` - takes a initialized instance
`toProvide()` - takes a `provider` function (instance constructor)
`withName()` - takes a string to name the instance. By this name, it will be possible to extract instance from the DI container
`singleton()` - sets a flag in the Binding that tells the DI container that there is only one dependency.
Example:
Пример:
```dart
// initializing a text string instance through a method toInstance()
Binding<String>().toInstance("hello world");
// or
// initializing a text string instance
Binding<String>().toProvide(() => "hello world");
// initializing an instance of a string named
Binding<String>().withName("my_string").toInstance("hello world");
// or
Binding<String>().withName("my_string").toProvide(() => "hello world");
// instance initialization like singleton
Binding<String>().toInstance("hello world");
// or
Binding<String>().toProvide(() => "hello world").singleton();
final container = DiContainer();
container.bind<SomeService>().toValue(SomeServiceImpl());
/*
...
*/
// Метод `resolve` просто возвращает зарегистрированный ранее экземпляр
final someService = container.resolve<SomeService>();
```
### Module
### Ленивая инициализация
Module is a container of user instances, and on the basis of which the user can create their modules. The user in his module must implement the `void builder (Scope currentScope)` method.
Если вам нужно создать объект в момент резолвинга, вы можете использовать ленивую (другими словами, по запросу) инициализацию объекта
с помощью метода `toFactoryN()`.
Example:
Пример:
```dart
class AppModule extends Module {
@override
void builder(Scope currentScope) {
bind<ApiClient>().toInstance(ApiClientMock());
}
final container = DiContainer();
// В методе `toFactory` вы просто определяете, как построить экземпляр через фабричную лямбду
container.bind<SomeService>().toFactory( () => SomeServiceImpl() );
/*
...
*/
// Метод `resolve()` будет создавать экземпляр через зарегистрированную фабричную лямбду каждый раз, когда вы вызываете его
final someService = container.resolve<SomeService>();
final anotherSomeService = container.resolve<SomeService>();
assert(someService != anotherSomeService);
```
Но обычно у вас есть много типов с разными зависимостями, которые образуют граф зависимостей.
Пример:
```dart
class A {}
class B {}
class C {
final A a;
final B b;
C(this.a, this.b);
}
```
### Scope
Scope is a container that stores the entire dependency tree (scope, modules, instances).
Through the scope, you can access the custom `instance`, for this you need to call the `resolve<T>()` method and specify the type of the object, and you can also pass additional parameters.
Если вам нужно зарегистрировать некоторый тип, зависящий от других типов из контейнера,
вы можете использовать методы `toFactory1<T1>` - `toFactory8<T1 ... T8>`, где число в конце,
является количеством запрошенных через аргументы типов зависимостей.
(Обратите внимание, что вам нужно определить все зависимости в аргументах - `toFactory2<A1, A2>`).
Example:
Пример:
```dart
// open main scope
final rootScope = Cherrypick.openRootScope();
class SomeService {
final A a;
final B b;
// initializing scope with a custom module
rootScope.installModules([AppModule()]);
SomeService(this.a, this.b);
}
// takes custom instance
final str = rootScope.resolve<String>();
// or
final str = rootScope.tryResolve<String>();
final container = DiContainer();
container.bind<A>(A::class).toFactory (() => A());
container.bind<B>(B::class).toFactory (() => B());
// close main scope
Cherrypick.closeRootScope();
/// В фабричной лямбде вы определяете, как построить зависимость от других зависимостей
/// (Порядок разрешенных экземпляров соответствует порядку типов аргументов)
container.bind<SomeService>().toFactory2<A, B>((a, b) => SomeService(a, b));
/*
...
*/
/// Получаем экземпляр `SomeService` через resolve своих зависимостей.
/// В нашем случае - это resolve A и B
/// Внимание!!! То, что он будет создавать новые экземпляры A и B каждый раз, когда вы вызываете `resolve` SomeService
final someService = container.resolve<SomeService>();
```
## Example app
### Время жизни экземпляров и контроль области видимости
Если вы хотите создать экземпляр зарегистрированной зависимости только один раз,
и вам нужно получить/разрешить зависимость много раз в контейнере, то вы можете зарегистрировать
свою зависимость с добавлением `asSingeton()`. Например:
```dart
final container = DiContainer();
container.bind<A>()
.toFactory(() => A())
.asSingleton();
container
.bind<B>()
.toFactory(() => B());
.asSingleton();
container.bind<SomeService>().toFactory2<A, B>((a, b) -> SomeService(a, b));
// Код выше означает: Контейнер, регистрирует создание A и B только в первый раз, когда оно будет запрошен,
// и регистрирует создание SomeService каждый раз, когда оно будет запрошен.
final a = container.resolve<A>();
final b = container.resolve<B>();
final anotherA = container.resolve<A>();
final anotherB = container.resolve<B>();
assert(a == anotherA && b == anotherB);
final someService = container.resolve<SomeService>();
final anotherSomeService = container.resolve<SomeService>();
assert(someService != anotherSomeService);
```
Если вы хотите сразу создать свой зарегистрированный экземпляр, вы можете вызвать `resolve()`. Например:
```dart
final container = DiContainer();
// Это заставит создать зависимость после регистрации
container.bind <SomeService>()
.toFactory(() => SomeService())
.asSingleton()
.resolve();
```
Когда вы работаете со сложным приложением, в большинстве случаев вы можете работать со многими модулями с собственными зависимостями.
Эти модули могут быть настроены различными `DiContainer`-ми. И вы можете прикрепить контейнер к другому, как родительский.
В этом случае родительские зависимости будут видны для дочернего контейнера,
и через него вы можете формировать различные области видимости зависимостей. Например:
```dart
final parentContainer = DiContainer();
parentContainer.bind<A>().toFactory(() => A())
final childContainer = DiContainer(parentContainer);
// Обратите внимание, что родительская зависимость A видна для дочернего контейнера
final a = childContainer.resolve<A>();
/*
// Но следующий код потерпит неудачу с ошибкой, потому что родитель не знает о своем потомке.
final parentContainer = DiContainer();
final childContainer = DiContainer();
childContainer.bind<A>().toFactory(() => A());
// Выдает ошибку
final a = parentContainer.resolve<A>();
*/
```
### Структура библиотеки
Библиотека состоит из DiContainer и Resolver.
DiContainer - это контейнер со всеми Resolver для разных типов. А `Resolver` - это просто объект, который знает, как разрешить данный тип.
Многие из resolver-ов обернуты другими, поэтому они могут быть составлены для разных вариантов использования.
Resolver - интерфейс, поэтому он имеет много реализаций. Основным является ResolvingContext.
Вы можете думать об этом как об объекте контекста, который имеет вспомогательные методы для создания различных вариантов resolver-ов (`toFactory`,` toValue`, `asSingleton`).
Но все они просто используют метод `toResolver` для определения некоторого корневого resolver в контексте.
Когда вы запрашиваете тип из контейнера с помощью метода `resolve<T>()`, он просто находит контекст для типа и вызывает корневой resolver, который может вызывать другие resolver-ы.
Пример (из ```example```):
```dart
import 'dart:async';
import 'package:meta/meta.dart';
import 'package:cherrypick/cherrypick.dart';
class AppModule extends Module {
@override
void builder(Scope currentScope) {
bind<ApiClient>().withName("apiClientMock").toInstance(ApiClientMock());
bind<ApiClient>().withName("apiClientImpl").toInstance(ApiClientImpl());
}
}
class FeatureModule extends Module {
bool isMock;
FeatureModule({required this.isMock});
@override
void builder(Scope currentScope) {
bind<DataRepository>()
.withName("networkRepo")
.toProvide(
() => NetworkDataRepository(
currentScope.resolve<ApiClient>(
named: isMock ? "apiClientMock" : "apiClientImpl",
),
),
)
.singleton();
bind<DataBloc>().toProvide(
() => DataBloc(
currentScope.resolve<DataRepository>(named: "networkRepo"),
),
);
}
}
import 'package:dart_di/dart_di.dart';
void main() async {
final scope = openRootScope().installModules([
AppModule(),
]);
final dataModule = new DiContainer()
..bind<ApiClient>().toValue(new ApiClientMock())
..bind<DataRepository>()
.toFactory1<ApiClient>((c) => new NetworkDataRepository(c))
..bind<DataBloc>().toFactory1<DataRepository>((s) => new DataBloc(s));
final subScope = scope
.openSubScope("featureScope")
.installModules([FeatureModule(isMock: true)]);
final dataBloc = subScope.resolve<DataBloc>();
final dataBloc = dataModule.resolve<DataBloc>();
dataBloc.data.listen((d) => print('Received data: $d'),
onError: (e) => print('Error: $e'), onDone: () => print('DONE'));
@@ -179,27 +240,8 @@ abstract class ApiClient {
class ApiClientMock implements ApiClient {
@override
Future sendRequest(
{@required String? url, String? token, Map? requestBody}) async {
return 'Local Data';
{@required String url, String token, Map requestBody}) async {
return 'hello world';
}
}
class ApiClientImpl implements ApiClient {
@override
Future sendRequest(
{@required String? url, String? token, Map? requestBody}) async {
return 'Network data';
}
}
```
[GitHub Linl](https://github.com/pese-git/cherrypick)
### Features
- [x] Scope
- [x] Sub scope
- [x] Initialization instance with name
```

View File

@@ -1 +0,0 @@
include: package:pedantic/analysis_options.yaml

View File

@@ -1,194 +0,0 @@
# Quick start
## Main components DI
### Binding
Binding is a custom instance configurator that contains methods for configuring a dependency.
There are two main methods for initializing a custom instance `toInstance ()` and `toProvide ()` and auxiliary `withName ()` and `singleton ()`.
`toInstance()` - takes a initialized instance
`toProvide()` -  takes a `provider` function (instance constructor)
`withName()` - takes a string to name the instance. By this name, it will be possible to extract instance from the DI container
`singleton()` - sets a flag in the Binding that tells the DI container that there is only one dependency.
Example:
```dart
// initializing a text string instance through a method toInstance()
Binding<String>().toInstance("hello world");
// or
// initializing a text string instance
Binding<String>().toProvide(() => "hello world");
// initializing an instance of a string named
Binding<String>().withName("my_string").toInstance("hello world");
// or
Binding<String>().withName("my_string").toProvide(() => "hello world");
// instance initialization like singleton
Binding<String>().toInstance("hello world");
// or
Binding<String>().toProvide(() => "hello world").singleton();
```
### Module
Module is a container of user instances, and on the basis of which the user can create their modules. The user in his module must implement the `void builder (Scope currentScope)` method.
Example:
```dart
class AppModule extends Module {
@override
void builder(Scope currentScope) {
bind<ApiClient>().toInstance(ApiClientMock());
}
}
```
### Scope
Scope is a container that stores the entire dependency tree (scope, modules, instances).
Through the scope, you can access the custom `instance`, for this you need to call the `resolve<T>()` method and specify the type of the object, and you can also pass additional parameters.
Example:
```dart
// open main scope
final rootScope = Cherrypick.openRootScope();
// initializing scope with a custom module
rootScope.installModules([AppModule()]);
// takes custom instance
final str = rootScope.resolve<String>();
// or
final str = rootScope.tryResolve<String>();
// close main scope
Cherrypick.closeRootScope();
```
## Example app
```dart
import 'dart:async';
import 'package:meta/meta.dart';
import 'package:cherrypick/cherrypick.dart';
class AppModule extends Module {
@override
void builder(Scope currentScope) {
bind<ApiClient>().withName("apiClientMock").toInstance(ApiClientMock());
bind<ApiClient>().withName("apiClientImpl").toInstance(ApiClientImpl());
}
}
class FeatureModule extends Module {
bool isMock;
FeatureModule({required this.isMock});
@override
void builder(Scope currentScope) {
bind<DataRepository>()
.withName("networkRepo")
.toProvide(
() => NetworkDataRepository(
currentScope.resolve<ApiClient>(
named: isMock ? "apiClientMock" : "apiClientImpl",
),
),
)
.singleton();
bind<DataBloc>().toProvide(
() => DataBloc(
currentScope.resolve<DataRepository>(named: "networkRepo"),
),
);
}
}
void main() async {
final scope = openRootScope().installModules([
AppModule(),
]);
final subScope = scope
.openSubScope("featureScope")
.installModules([FeatureModule(isMock: true)]);
final dataBloc = subScope.resolve<DataBloc>();
dataBloc.data.listen((d) => print('Received data: $d'),
onError: (e) => print('Error: $e'), onDone: () => print('DONE'));
await dataBloc.fetchData();
}
class DataBloc {
final DataRepository _dataRepository;
Stream<String> get data => _dataController.stream;
StreamController<String> _dataController = new StreamController.broadcast();
DataBloc(this._dataRepository);
Future<void> fetchData() async {
try {
_dataController.sink.add(await _dataRepository.getData());
} catch (e) {
_dataController.sink.addError(e);
}
}
void dispose() {
_dataController.close();
}
}
abstract class DataRepository {
Future<String> getData();
}
class NetworkDataRepository implements DataRepository {
final ApiClient _apiClient;
final _token = 'token';
NetworkDataRepository(this._apiClient);
@override
Future<String> getData() async => await _apiClient.sendRequest(
url: 'www.google.com', token: _token, requestBody: {'type': 'data'});
}
abstract class ApiClient {
Future sendRequest({@required String url, String token, Map requestBody});
}
class ApiClientMock implements ApiClient {
@override
Future sendRequest(
{@required String? url, String? token, Map? requestBody}) async {
return 'Local Data';
}
}
class ApiClientImpl implements ApiClient {
@override
Future sendRequest(
{@required String? url, String? token, Map? requestBody}) async {
return 'Network data';
}
}
```

View File

@@ -1,194 +0,0 @@
# Быстрый старт
## Основные компоненты DI
### Binding
Binding - по сути это конфигуратор для пользовательского instance, который соддержит методы для конфигурирования зависимости.
Есть два основных метода для инициализации пользовательского instance `toInstance()` и `toProvide()` и вспомогательных `withName()` и `singleton()`.
`toInstance()` - принимает готовый экземпляр
`toProvide()` -  принимает функцию `provider` (конструктор экземпляра)
`withName()` - принимает строку для именования экземпляра. По этому имени можно будет извлечь instance из DI контейнера
`singleton()` - устанавливает флаг в Binding, который говорит DI контейнеру, что зависимость одна.
Пример:
```dart
// инициализация экземпляра текстовой строки через метод toInstance()
Binding<String>().toInstance("hello world");
// или
// инициализация экземпляра текстовой строки
Binding<String>().toProvide(() => "hello world");
// инициализация экземпляра строки с именем
Binding<String>().withName("my_string").toInstance("hello world");
// или
Binding<String>().withName("my_string").toProvide(() => "hello world");
// инициализация экземпляра, как сингелтон
Binding<String>().toInstance("hello world");
// или
Binding<String>().toProvide(() => "hello world").singleton();
```
### Module
Module - это контейнер пользовательских instances, и на основе которого пользователь может создавать свои модули. Пользователь в своем модуле должен реализовать метод `void builder(Scope currentScope)`.
Пример:
```dart
class AppModule extends Module {
@override
void builder(Scope currentScope) {
bind<ApiClient>().toInstance(ApiClientMock());
}
}
```
### Scope
Scope - это контейнер, который хранит все дерево зависимостей (scope,modules,instances).
Через scope можно получить доступ к `instance`, для этого нужно вызвать метод `resolve<T>()` и указать тип объекта, а так же можно передать дополнительные параметры.
Пример:
```dart
// открыть главный scope
final rootScope = CherryPick.openRootScope();
// инициализация scope пользовательским модулем
rootScope.installModules([AppModule()]);
// получаем экземпляр класса
final str = rootScope.resolve<String>();
// или
final str = rootScope.tryResolve<String>();
// закрыть главный scope
Cherrypick.closeRootScope();
```
## Пример приложения
```dart
import 'dart:async';
import 'package:meta/meta.dart';
import 'package:cherrypick/cherrypick.dart';
class AppModule extends Module {
@override
void builder(Scope currentScope) {
bind<ApiClient>().withName("apiClientMock").toInstance(ApiClientMock());
bind<ApiClient>().withName("apiClientImpl").toInstance(ApiClientImpl());
}
}
class FeatureModule extends Module {
bool isMock;
FeatureModule({required this.isMock});
@override
void builder(Scope currentScope) {
bind<DataRepository>()
.withName("networkRepo")
.toProvide(
() => NetworkDataRepository(
currentScope.resolve<ApiClient>(
named: isMock ? "apiClientMock" : "apiClientImpl",
),
),
)
.singleton();
bind<DataBloc>().toProvide(
() => DataBloc(
currentScope.resolve<DataRepository>(named: "networkRepo"),
),
);
}
}
void main() async {
final scope = openRootScope().installModules([
AppModule(),
]);
final subScope = scope
.openSubScope("featureScope")
.installModules([FeatureModule(isMock: true)]);
final dataBloc = subScope.resolve<DataBloc>();
dataBloc.data.listen((d) => print('Received data: $d'),
onError: (e) => print('Error: $e'), onDone: () => print('DONE'));
await dataBloc.fetchData();
}
class DataBloc {
final DataRepository _dataRepository;
Stream<String> get data => _dataController.stream;
StreamController<String> _dataController = new StreamController.broadcast();
DataBloc(this._dataRepository);
Future<void> fetchData() async {
try {
_dataController.sink.add(await _dataRepository.getData());
} catch (e) {
_dataController.sink.addError(e);
}
}
void dispose() {
_dataController.close();
}
}
abstract class DataRepository {
Future<String> getData();
}
class NetworkDataRepository implements DataRepository {
final ApiClient _apiClient;
final _token = 'token';
NetworkDataRepository(this._apiClient);
@override
Future<String> getData() async => await _apiClient.sendRequest(
url: 'www.google.com', token: _token, requestBody: {'type': 'data'});
}
abstract class ApiClient {
Future sendRequest({@required String url, String token, Map requestBody});
}
class ApiClientMock implements ApiClient {
@override
Future sendRequest(
{@required String? url, String? token, Map? requestBody}) async {
return 'Local Data';
}
}
class ApiClientImpl implements ApiClient {
@override
Future sendRequest(
{@required String? url, String? token, Map? requestBody}) async {
return 'Network data';
}
}
```

View File

@@ -1,133 +0,0 @@
# Example
pubspec.yaml:
```yaml
name: example
version: 1.0.0
environment:
sdk: ">=2.12.0 <3.0.0"
dependencies:
cherrypick:
path: ../
dev_dependencies:
test: ^1.16.8
```
main.dart:
```dart
import 'dart:async';
import 'package:meta/meta.dart';
import 'package:cherrypick/scope.dart';
import 'package:cherrypick/module.dart';
class AppModule extends Module {
@override
void builder(Scope currentScope) {
bind<ApiClient>().withName("apiClientMock").toInstance(ApiClientMock());
bind<ApiClient>().withName("apiClientImpl").toInstance(ApiClientImpl());
}
}
class FeatureModule extends Module {
bool isMock;
FeatureModule({required this.isMock});
@override
void builder(Scope currentScope) {
bind<DataRepository>()
.withName("networkRepo")
.toProvide(
() => NetworkDataRepository(
currentScope.resolve<ApiClient>(
named: isMock ? "apiClientMock" : "apiClientImpl",
),
),
)
.singleton();
bind<DataBloc>().toProvide(
() => DataBloc(
currentScope.resolve<DataRepository>(named: "networkRepo"),
),
);
}
}
void main() async {
final scope = openRootScope().installModules([
AppModule(),
]);
final subScope = scope
.openSubScope("featureScope")
.installModules([FeatureModule(isMock: true)]);
final dataBloc = subScope.resolve<DataBloc>();
dataBloc.data.listen((d) => print('Received data: $d'),
onError: (e) => print('Error: $e'), onDone: () => print('DONE'));
await dataBloc.fetchData();
}
class DataBloc {
final DataRepository _dataRepository;
Stream<String> get data => _dataController.stream;
StreamController<String> _dataController = new StreamController.broadcast();
DataBloc(this._dataRepository);
Future<void> fetchData() async {
try {
_dataController.sink.add(await _dataRepository.getData());
} catch (e) {
_dataController.sink.addError(e);
}
}
void dispose() {
_dataController.close();
}
}
abstract class DataRepository {
Future<String> getData();
}
class NetworkDataRepository implements DataRepository {
final ApiClient _apiClient;
final _token = 'token';
NetworkDataRepository(this._apiClient);
@override
Future<String> getData() async => await _apiClient.sendRequest(
url: 'www.google.com', token: _token, requestBody: {'type': 'data'});
}
abstract class ApiClient {
Future sendRequest({@required String url, String token, Map requestBody});
}
class ApiClientMock implements ApiClient {
@override
Future sendRequest(
{@required String? url, String? token, Map? requestBody}) async {
return 'Local Data';
}
}
class ApiClientImpl implements ApiClient {
@override
Future sendRequest(
{@required String? url, String? token, Map? requestBody}) async {
return 'Network data';
}
}
```

View File

@@ -1,53 +1,15 @@
import 'dart:async';
import 'package:cherrypick/cherrypick.dart';
import 'package:meta/meta.dart';
class AppModule extends Module {
@override
void builder(Scope currentScope) {
bind<ApiClient>().withName('apiClientMock').toInstance(ApiClientMock());
bind<ApiClient>().withName('apiClientImpl').toInstance(ApiClientImpl());
}
}
class FeatureModule extends Module {
bool isMock;
FeatureModule({required this.isMock});
@override
void builder(Scope currentScope) {
bind<DataRepository>()
.withName('networkRepo')
.toProvide(
() => NetworkDataRepository(
currentScope.resolve<ApiClient>(
named: isMock ? 'apiClientMock' : 'apiClientImpl',
),
),
)
.singleton();
bind<DataBloc>().toProvideWithParams(
(param) => DataBloc(
currentScope.resolve<DataRepository>(named: 'networkRepo'),
param,
),
);
}
}
import 'package:dart_di/dart_di.dart';
void main() async {
final scope = openRootScope().installModules([
AppModule(),
]);
final dataModule = new DiContainer()
..bind<ApiClient>().toValue(new ApiClientMock())
..bind<DataRepository>()
.toFactory1<ApiClient>((c) => new NetworkDataRepository(c))
..bind<DataBloc>().toFactory1<DataRepository>((s) => new DataBloc(s));
final subScope = scope
.openSubScope('featureScope')
.installModules([FeatureModule(isMock: true)]);
final dataBloc = subScope.resolve<DataBloc>(params: 'PARAMETER');
final dataBloc = dataModule.resolve<DataBloc>();
dataBloc.data.listen((d) => print('Received data: $d'),
onError: (e) => print('Error: $e'), onDone: () => print('DONE'));
@@ -58,15 +20,13 @@ class DataBloc {
final DataRepository _dataRepository;
Stream<String> get data => _dataController.stream;
final StreamController<String> _dataController = StreamController.broadcast();
StreamController<String> _dataController = new StreamController.broadcast();
final String param;
DataBloc(this._dataRepository, this.param);
DataBloc(this._dataRepository);
Future<void> fetchData() async {
try {
_dataController.sink.add(await _dataRepository.getData(param));
_dataController.sink.add(await _dataRepository.getData());
} catch (e) {
_dataController.sink.addError(e);
}
@@ -78,7 +38,7 @@ class DataBloc {
}
abstract class DataRepository {
Future<String> getData(String param);
Future<String> getData();
}
class NetworkDataRepository implements DataRepository {
@@ -88,42 +48,18 @@ class NetworkDataRepository implements DataRepository {
NetworkDataRepository(this._apiClient);
@override
Future<String> getData(String param) async => await _apiClient.sendRequest(
url: 'www.google.com',
token: _token,
requestBody: {'type': 'data'},
param: param);
Future<String> getData() async => await _apiClient.sendRequest(
url: 'www.google.com', token: _token, requestBody: {'type': 'data'});
}
abstract class ApiClient {
Future sendRequest({
@required String url,
String token,
Map requestBody,
String param,
});
Future sendRequest({@required String url, String token, Map requestBody});
}
class ApiClientMock implements ApiClient {
@override
Future sendRequest({
@required String? url,
String? token,
Map? requestBody,
String? param,
}) async {
return 'Local Data $param';
}
}
class ApiClientImpl implements ApiClient {
@override
Future sendRequest({
@required String? url,
String? token,
Map? requestBody,
String? param,
}) async {
return 'Network data $param';
Future sendRequest(
{@required String? url, String? token, Map? requestBody}) async {
return 'hello world';
}
}

View File

@@ -1,14 +1,14 @@
name: example
version: 1.0.0
author: "Sergey Penkovsky <sergey.penkovsky@gmail.com>"
homepage: localhost
publish_to: none
environment:
sdk: ">=2.12.0 <3.0.0"
dependencies:
cherrypick:
dart_di:
path: ../
dev_dependencies:

View File

@@ -1,19 +0,0 @@
///
/// Copyright 2021 Sergey Penkovsky <sergey.penkovsky@gmail.com>
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
/// http://www.apache.org/licenses/LICENSE-2.0
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
library cherrypick;
export 'package:cherrypick/src/binding.dart';
export 'package:cherrypick/src/helper.dart';
export 'package:cherrypick/src/module.dart';
export 'package:cherrypick/src/scope.dart';

5
lib/dart_di.dart Normal file
View File

@@ -0,0 +1,5 @@
library dart_di;
export 'package:dart_di/di_container.dart';
export 'package:dart_di/resolvers/resolvers.dart';
export 'package:dart_di/resolvers/resolving_context.dart';

75
lib/di_container.dart Normal file
View File

@@ -0,0 +1,75 @@
import 'package:dart_di/resolvers/resolving_context.dart';
/**
* Контейнер - это объект, которой хранит все резолверы зависимостей.
*/
class DiContainer {
final DiContainer? _parent;
final _resolvers = <Type, ResolvingContext>{};
DiContainer([this._parent]);
/**
* Добавляет resolver зависимостей типа [T] в контейнер.
* Обратите внимание, что перезапись значений внутри одного контейнера запрещена.
* @return - возвращает [ResolvingContext] или [StateError]
*/
ResolvingContext<T> bind<T>() {
var context = ResolvingContext<T>(this);
if (hasInTree<T>()) {
throw StateError(
'Dependency of type `$T` is already exist in containers tree');
}
_resolvers[T] = context;
return context;
}
/**
* Возвращает разрешенную зависимость, определенную параметром типа [T].
* Выдает [StateError], если зависимость не может быть разрешена.
* Если вы хотите получить [null], если зависимость не может быть разрешена,
* то используйте вместо этого [tryResolve]
* @return - возвращает объект типа [T] или [StateError]
*/
T resolve<T>() {
var resolved = tryResolve<T>();
if (resolved != null) {
return resolved;
} else {
throw StateError(
'Can\'t resolve dependency `$T`. Maybe you forget register it?');
}
}
/**
* Возвращает разрешенную зависимость типа [T] или null, если она не может быть разрешена.
*/
T? tryResolve<T>() {
var resolver = _resolvers[T];
if (resolver != null) {
return resolver.resolve();
} else {
return _parent?.tryResolve<T>();
}
}
/**
* Возвращает true, если у этого контейнера есть средство разрешения зависимостей для типа [T].
* Если вы хотите проверить его для всего дерева контейнеров, используйте вместо него [hasInTree].
* @return - возвращает булево значение
*/
bool has<T>() {
return _resolvers.containsKey(T);
}
/**
* Возвращает true, если контейнер или его родители содержат средство разрешения зависимостей для типа [T].
* Если вы хотите проверить его только для этого контейнера, используйте вместо него [has].
* @return - возвращает булево значение
*/
bool hasInTree<T>() {
return has<T>() || (_parent != null && _parent!.hasInTree<T>());
}
}

View File

@@ -0,0 +1,15 @@
import 'package:dart_di/resolvers/resolver.dart';
/**
* Разрешает зависимость для фабричной функции
*/
class FactoryResolver<T> extends Resolver<T> {
final T Function() _factory;
FactoryResolver(this._factory);
@override
T resolve() {
return _factory();
}
}

View File

@@ -0,0 +1,11 @@
/**
* Resolver - это абстракция, которая определяет,
* как контейнер будет разрешать зависимость
*/
abstract class Resolver<T> {
/**
* Разрешает зависимость типа [T]
* @return - возвращает объект типа [T]
*/
T? resolve();
}

View File

@@ -0,0 +1,7 @@
/// Resolvers определяет, как разрешить зависимость для контейнера
library resolvers;
export 'resolver.dart';
export 'factory_resolver.dart';
export 'value_resolver.dart';
export 'singelton_resolver.dart';

View File

@@ -0,0 +1,172 @@
import 'package:dart_di/di_container.dart';
import 'package:dart_di/resolvers/factory_resolver.dart';
import 'package:dart_di/resolvers/resolver.dart';
import 'package:dart_di/resolvers/singelton_resolver.dart';
import 'package:dart_di/resolvers/value_resolver.dart';
class ResolvingContext<T> extends Resolver {
/// Корневой резолвер
Resolver<T> get resolver {
return _resolver as Resolver<T>;
}
DiContainer _container;
late Resolver _resolver;
ResolvingContext(this._container);
/**
* Разрешает зависимость типа [T]
* @return - возвращает объект типа [T]
*/
@override
T resolve() {
_verify();
return _resolver.resolve();
}
/**
* Добавляет резолвер в качестве корневого резолвера
* С помощью этого метода вы можете добавить любой
* пользовательский резолвер
*/
ResolvingContext<T> toResolver<TImpl extends T>(Resolver<TImpl> resolver) {
_resolver = resolver;
return this;
}
/**
* Создать резолвер значения
*/
ResolvingContext<T> toValue<TImpl extends T>(T value) {
Resolver<TImpl> resolver = ValueResolver(value as TImpl);
return toResolver<TImpl>(resolver);
}
/**
* Преобразователь в сингелтон
*/
ResolvingContext<T> asSingleton() {
return toResolver(SingletonResolver<T>(resolver));
}
/**
* Создать фабричный resolver без каких-либо зависимостей
*/
ResolvingContext<T> toFactory<TImpl extends T>(TImpl Function() factory) {
Resolver<TImpl> resolver = FactoryResolver<TImpl>(factory);
return toResolver(resolver);
}
/**
* Создать фабричный resolver с 1 зависимостью от контейнера
*/
ResolvingContext<T> toFactory1<T1>(T Function(T1) factory) {
Resolver<T> resolver =
FactoryResolver<T>(() => factory(_container.resolve<T1>()));
return toResolver(resolver);
}
/**
* Создать фабричный resolver с 2 зависимостями от контейнера
*/
ResolvingContext<T> toFactory2<T1, T2>(T Function(T1, T2) factory) {
Resolver<T> resolver = FactoryResolver<T>(
() => factory(_container.resolve<T1>(), _container.resolve<T2>()));
return toResolver(resolver);
}
/**
* Создать фабричный resolver с 3 зависимостями от контейнера
*/
ResolvingContext<T> toFactory3<T1, T2, T3>(T Function(T1, T2, T3) factory) {
Resolver<T> resolver = FactoryResolver<T>(() => factory(
_container.resolve<T1>(),
_container.resolve<T2>(),
_container.resolve<T3>()));
return toResolver(resolver);
}
/**
* Создать фабричный resolver с 4 зависимостями от контейнера
*/
ResolvingContext<T> toFactory4<T1, T2, T3, T4>(
T Function(T1, T2, T3, T4) factory) {
Resolver<T> resolver = FactoryResolver<T>(() => factory(
_container.resolve<T1>(),
_container.resolve<T2>(),
_container.resolve<T3>(),
_container.resolve<T4>()));
return toResolver(resolver);
}
/**
* Создать фабричный resolver с 5 зависимостями от контейнера
*/
ResolvingContext<T> toFactory5<T1, T2, T3, T4, T5>(
T Function(T1, T2, T3, T4, T5) factory) {
Resolver<T> resolver = FactoryResolver<T>(() => factory(
_container.resolve<T1>(),
_container.resolve<T2>(),
_container.resolve<T3>(),
_container.resolve<T4>(),
_container.resolve<T5>()));
return toResolver(resolver);
}
/**
* Создать фабричный resolver с 6 зависимостями от контейнера
*/
ResolvingContext<T> toFactory6<T1, T2, T3, T4, T5, T6>(
T Function(T1, T2, T3, T4, T5, T6) factory) {
Resolver<T> resolver = FactoryResolver<T>(() => factory(
_container.resolve<T1>(),
_container.resolve<T2>(),
_container.resolve<T3>(),
_container.resolve<T4>(),
_container.resolve<T5>(),
_container.resolve<T6>()));
return toResolver(resolver);
}
/**
* Создать фабричный resolver с 7 зависимостями от контейнера
*/
ResolvingContext<T> toFactory7<T1, T2, T3, T4, T5, T6, T7>(
T Function(T1, T2, T3, T4, T5, T6, T7) factory) {
Resolver<T> resolver = FactoryResolver<T>(() => factory(
_container.resolve<T1>(),
_container.resolve<T2>(),
_container.resolve<T3>(),
_container.resolve<T4>(),
_container.resolve<T5>(),
_container.resolve<T6>(),
_container.resolve<T7>()));
return toResolver(resolver);
}
/**
* Создать фабричный resolver с 8 зависимостями от контейнера
*/
ResolvingContext<T> toFactory8<T1, T2, T3, T4, T5, T6, T7, T8>(
T Function(T1, T2, T3, T4, T5, T6, T7, T8) factory) {
Resolver<T> resolver = FactoryResolver<T>(() => factory(
_container.resolve<T1>(),
_container.resolve<T2>(),
_container.resolve<T3>(),
_container.resolve<T4>(),
_container.resolve<T5>(),
_container.resolve<T6>(),
_container.resolve<T7>(),
_container.resolve<T8>()));
return toResolver(resolver);
}
void _verify() {
if (_resolver == null) {
throw StateError("Can\'t resolve T without any resolvers. " +
"Please check, may be you didn\'t do anything after bind()");
}
}
}

View File

@@ -0,0 +1,16 @@
import 'package:dart_di/resolvers/resolver.dart';
class SingletonResolver<T> extends Resolver<T> {
Resolver<T> _decoratedResolver;
T? _value = null;
SingletonResolver(this._decoratedResolver);
@override
T? resolve() {
if (_value == null) {
_value = _decoratedResolver.resolve();
}
return _value;
}
}

View File

@@ -0,0 +1,15 @@
import 'package:dart_di/resolvers/resolver.dart';
/**
* Разрешает зависимость для значения
*/
class ValueResolver<T> extends Resolver<T> {
T _value;
ValueResolver(this._value);
@override
T resolve() {
return _value;
}
}

View File

@@ -1,142 +0,0 @@
///
/// Copyright 2021 Sergey Penkovsky <sergey.penkovsky@gmail.com>
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
/// http://www.apache.org/licenses/LICENSE-2.0
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
enum Mode { SIMPLE, INSTANCE, PROVIDER_INSTANCE, PROVIDER_WITH_PARAMS_INSTANCE }
typedef ProviderWithParams<T> = T Function(dynamic params);
/// RU: Класс Binding<T> настраивает параметры экземпляра.
/// ENG: The Binding<T> class configures the settings for the instance.
///
class Binding<T> {
late Mode _mode;
late Type _key;
late String _name;
T? _instance;
T? Function()? _provider;
ProviderWithParams<T>? _providerWithParams;
late bool _isSingleton = false;
late bool _isNamed = false;
Binding() {
_mode = Mode.SIMPLE;
_key = T;
}
/// RU: Метод возвращает [Mode] экземпляра.
/// ENG: The method returns the [Mode] of the instance.
///
/// return [Mode]
Mode get mode => _mode;
/// RU: Метод возвращает тип экземпляра.
/// ENG: The method returns the type of the instance.
///
/// return [Type]
Type get key => _key;
/// RU: Метод возвращает имя экземпляра.
/// ENG: The method returns the name of the instance.
///
/// return [String]
String get name => _name;
/// RU: Метод проверяет сингелтон экземпляр или нет.
/// ENG: The method checks the singleton instance or not.
///
/// return [bool]
bool get isSingleton => _isSingleton;
/// RU: Метод проверяет именован экземпляр или нет.
/// ENG: The method checks whether the instance is named or not.
///
/// return [bool]
bool get isNamed => _isNamed;
/// RU: Добавляет имя для экземляпя [value].
/// ENG: Added name for instance [value].
///
/// return [Binding]
Binding<T> withName(String name) {
_name = name;
_isNamed = true;
return this;
}
/// RU: Инициализация экземляпяра [value].
/// ENG: Initialization instance [value].
///
/// return [Binding]
Binding<T> toInstance(T value) {
_mode = Mode.INSTANCE;
_instance = value;
_isSingleton = true;
return this;
}
/// RU: Инициализация экземляпяра  через провайдер [value].
/// ENG: Initialization instance via provider [value].
///
/// return [Binding]
Binding<T> toProvide(T Function() value) {
_mode = Mode.PROVIDER_INSTANCE;
_provider = value;
return this;
}
/// RU: Инициализация экземляпяра  через провайдер [value] c динамическим параметром.
/// ENG: Initialization instance via provider [value] with a dynamic param.
///
/// return [Binding]
Binding<T> toProvideWithParams(ProviderWithParams<T> value) {
_mode = Mode.PROVIDER_WITH_PARAMS_INSTANCE;
_providerWithParams = value;
return this;
}
/// RU: Инициализация экземляпяра  как сингелтон [value].
/// ENG: Initialization instance as a singelton [value].
///
/// return [Binding]
Binding<T> singleton() {
_isSingleton = true;
return this;
}
/// RU: Поиск экземпляра.
/// ENG: Resolve instance.
///
/// return [T]
T? get instance => _instance;
/// RU: Поиск экземпляра.
/// ENG: Resolve instance.
///
/// return [T]
T? get provider {
if (_isSingleton) {
_instance ??= _provider?.call();
return _instance;
}
return _provider?.call();
}
/// RU: Поиск экземпляра с параметром.
///
/// ENG: Resolve instance with [params].
///
/// return [T]
T? providerWithParams(dynamic params) {
return _providerWithParams?.call(params);
}
}

View File

@@ -1,17 +0,0 @@
///
/// Copyright 2021 Sergey Penkovsky <sergey.penkovsky@gmail.com>
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
/// http://www.apache.org/licenses/LICENSE-2.0
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import 'package:cherrypick/src/scope.dart';
abstract class Factory<T> {
T createInstance(Scope scope);
}

View File

@@ -1,105 +0,0 @@
///
/// Copyright 2021 Sergey Penkovsky <sergey.penkovsky@gmail.com>
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
/// http://www.apache.org/licenses/LICENSE-2.0
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import 'package:cherrypick/src/scope.dart';
import 'package:meta/meta.dart';
Scope? _rootScope;
class CherryPick {
/// RU: Метод открывает главный [Scope].
/// ENG: The method opens the main [Scope].
///
/// return
static Scope openRootScope() {
_rootScope ??= Scope(null);
return _rootScope!;
}
/// RU: Метод закрывает главный [Scope].
/// ENG: The method close the main [Scope].
///
///
static void closeRootScope() {
if (_rootScope != null) {
_rootScope = null;
}
}
/// RU: Метод открывает дочерний [Scope].
/// ENG: The method open the child [Scope].
///
/// Дочерний [Scope] открывается с [scopeName]
/// Child [Scope] open with [scopeName]
///
/// Example:
/// ```
/// final String scopeName = 'firstScope.secondScope';
/// final subScope = CherryPick.openScope(scopeName);
/// ```
///
///
@experimental
static Scope openScope({String scopeName = '', String separator = '.'}) {
if (scopeName.isEmpty) {
return openRootScope();
}
final nameParts = scopeName.split(separator);
if (nameParts.isEmpty) {
throw Exception('Can not open sub scope because scopeName can not split');
}
return nameParts.fold(
openRootScope(),
(Scope previousValue, String element) =>
previousValue.openSubScope(element));
}
/// RU: Метод открывает дочерний [Scope].
/// ENG: The method open the child [Scope].
///
/// Дочерний [Scope] открывается с [scopeName]
/// Child [Scope] open with [scopeName]
///
/// Example:
/// ```
/// final String scopeName = 'firstScope.secondScope';
/// final subScope = CherryPick.closeScope(scopeName);
/// ```
///
///
@experimental
static void closeScope({String scopeName = '', String separator = '.'}) {
if (scopeName.isEmpty) {
closeRootScope();
}
final nameParts = scopeName.split(separator);
if (nameParts.isEmpty) {
throw Exception(
'Can not close sub scope because scopeName can not split');
}
if (nameParts.length > 1) {
final lastPart = nameParts.removeLast();
final scope = nameParts.fold(
openRootScope(),
(Scope previousValue, String element) =>
previousValue.openSubScope(element));
scope.closeSubScope(lastPart);
} else {
openRootScope().closeSubScope(nameParts[0]);
}
}
}

View File

@@ -1,53 +0,0 @@
///
/// Copyright 2021 Sergey Penkovsky <sergey.penkovsky@gmail.com>
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
/// http://www.apache.org/licenses/LICENSE-2.0
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import 'dart:collection';
import 'package:cherrypick/src/binding.dart';
import 'package:cherrypick/src/scope.dart';
/// RU: Класс Module является основой для пользовательских модулей.
/// Этот класс нужен для инициализации [Scope].
///
/// RU: The Module class is the basis for custom modules.
/// This class is needed to initialize [Scope].
///
abstract class Module {
final Set<Binding> _bindingSet = HashSet();
/// RU: Метод добавляет в коллекцию модуля [Binding] экземпляр.
///
/// ENG: The method adds an instance to the collection of the [Binding] module.
///
/// return [Binding<T>]
Binding<T> bind<T>() {
final binding = Binding<T>();
_bindingSet.add(binding);
return binding;
}
/// RU: Метод возвращает коллекцию [Binding] экземпляров.
///
/// ENG: The method returns a collection of [Binding] instances.
///
/// return [Set<Binding>]
Set<Binding> get bindingSet => _bindingSet;
/// RU: Абстрактный метод для инициализации пользовательских экземпляров.
/// В этом методе осуществляется конфигурация зависимостей.
///
/// ENG: Abstract method for initializing custom instances.
/// This method configures dependencies.
///
/// return [void]
void builder(Scope currentScope);
}

View File

@@ -1,131 +0,0 @@
///
/// Copyright 2021 Sergey Penkovsky <sergey.penkovsky@gmail.com>
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
/// http://www.apache.org/licenses/LICENSE-2.0
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import 'dart:collection';
import 'package:cherrypick/src/binding.dart';
import 'package:cherrypick/src/module.dart';
Scope openRootScope() => Scope(null);
class Scope {
final Scope? _parentScope;
/// RU: Метод возвращает родительский [Scope].
///
/// ENG: The method returns the parent [Scope].
///
/// return [Scope]
Scope? get parentScope => _parentScope;
final Map<String, Scope> _scopeMap = HashMap();
Scope(this._parentScope);
final Set<Module> _modulesList = HashSet();
/// RU: Метод открывает дочерний (дополнительный) [Scope].
///
/// ENG: The method opens child (additional) [Scope].
///
/// return [Scope]
Scope openSubScope(String name) {
if (!_scopeMap.containsKey(name)) {
_scopeMap[name] = Scope(this);
}
return _scopeMap[name]!;
}
/// RU: Метод закрывает дочерний (дополнительный) [Scope].
///
/// ENG: The method closes child (additional) [Scope].
///
/// return [Scope]
void closeSubScope(String name) {
_scopeMap.remove(name);
}
/// RU: Метод инициализирует пользовательские модули в [Scope].
///
/// ENG: The method initializes custom modules in [Scope].
///
/// return [Scope]
Scope installModules(List<Module> modules) {
_modulesList.addAll(modules);
modules.forEach((module) => module.builder(this));
return this;
}
/// RU: Метод удаляет пользовательские модули из [Scope].
///
/// ENG: This method removes custom modules from [Scope].
///
/// return [Scope]
Scope dropModules() {
_modulesList.removeAll(_modulesList);
return this;
}
/// RU: Возвращает найденную зависимость, определенную параметром типа [T].
/// Выдает [StateError], если зависимость не может быть разрешена.
/// Если вы хотите получить [null], если зависимость не может быть найдена,
/// то используйте вместо этого [tryResolve]
/// return - возвращает объект типа [T] или [StateError]
///
/// ENG: Returns the found dependency specified by the type parameter [T].
/// Throws [StateError] if the dependency cannot be resolved.
/// If you want to get [null] if the dependency cannot be found then use [tryResolve] instead
/// return - returns an object of type [T] or [StateError]
///
T resolve<T>({String? named, dynamic params}) {
var resolved = tryResolve<T>(named: named, params: params);
if (resolved != null) {
return resolved;
} else {
throw StateError(
'Can\'t resolve dependency `$T`. Maybe you forget register it?');
}
}
/// RU: Возвращает найденную зависимость типа [T] или null, если она не может быть найдена.
/// ENG: Returns found dependency of type [T] or null if it cannot be found.
///
T? tryResolve<T>({String? named, dynamic params}) {
// 1 Поиск зависимости по всем модулям текущего скоупа
if (_modulesList.isNotEmpty) {
for (var module in _modulesList) {
for (var binding in module.bindingSet) {
if (binding.key == T &&
((!binding.isNamed && named == null) ||
(binding.isNamed && named == binding.name))) {
switch (binding.mode) {
case Mode.INSTANCE:
return binding.instance;
case Mode.PROVIDER_INSTANCE:
return binding.provider;
case Mode.PROVIDER_WITH_PARAMS_INSTANCE:
if (params == null) {
throw StateError('Param is null. Maybe you forget pass it');
}
return binding.providerWithParams(params);
default:
return null;
}
}
}
}
}
// 2 Поиск зависимостей в родительском скоупе
return _parentScope != null ? _parentScope!.tryResolve(named: named) : null;
}
}

View File

@@ -1,10 +1,8 @@
name: cherrypick
description: Cherrypick is a small dependency injection (DI) library for dart/flutter projects.
version: 1.0.3
homepage: https://pese-git.github.io/cherrypick-site/
documentation: https://github.com/pese-git/cherrypick/wiki
repository: https://github.com/pese-git/cherrypick
issue_tracker: https://github.com/pese-git/cherrypick/issues
name: dart_di
description: Experimental Dependency Injection library.
version: 0.0.2
author: Sergey Penkovsky <sergey.penkovsky@gmail.com>
homepage: locahost
environment:
sdk: ">=2.12.0 <3.0.0"
@@ -13,8 +11,6 @@ dependencies:
meta: ^1.3.0
dev_dependencies:
pedantic: ^1.11.0
test: ^1.16.8
test: ^1.17.2
mockito: ^5.0.6
mockito: ^5.0.3

458
test/di_container_test.dart Normal file
View File

@@ -0,0 +1,458 @@
import 'package:dart_di/di_container.dart';
import 'package:dart_di/resolvers/resolver.dart';
import 'package:test/test.dart';
import 'package:mockito/mockito.dart';
void main() {
group('Without parent', () {
test('Container bind<T> throws state error if it\'s already has resolver',
() {
final container = new DiContainer();
container.bind<int>().toResolver(_makeResolver(5));
expect(() => container.bind<int>().toResolver(_makeResolver(3)),
throwsA(isA<StateError>()));
});
test("Container resolves value after adding a dependency", () {
final expectedValue = 3;
final container = new DiContainer();
container.bind<int>().toResolver(_makeResolver(expectedValue));
expect(container.resolve<int>(), expectedValue);
});
test("Container throws state error if the value can't be resolved", () {
final container = DiContainer();
expect(() => container.resolve<int>(), throwsA(isA<StateError>()));
});
test("Container has() returns true if it has resolver", () {
final expectedValue = 5;
final container = new DiContainer();
container.bind<int>().toResolver(_makeResolver(expectedValue));
expect(container.has<int>(), true);
});
test("Container has() returns false if it hasn't resolver", () {
final container = new DiContainer();
expect(container.has<int>(), false);
});
test("Container hasInTree() returns true if it has resolver", () {
final expectedValue = 5;
final container = DiContainer();
container.bind<int>().toResolver(_makeResolver(expectedValue));
expect(container.hasInTree<int>(), true);
});
test("Container hasInTree() returns true if it hasn`t resolver", () {
final container = DiContainer();
expect(container.hasInTree<int>(), false);
});
});
group('With parent', () {
test(
"Container bind() throws state error (if it's parent already has a resolver)",
() {
final parentContainer = new DiContainer();
final container = new DiContainer(parentContainer);
parentContainer.bind<int>().toResolver(_makeResolver(5));
expect(() => container.bind<int>().toResolver(_makeResolver(3)),
throwsA(isA<StateError>()));
});
test("Container resolve() returns a value from parent container", () {
final expectedValue = 5;
final parentContainer = DiContainer();
final container = DiContainer(parentContainer);
parentContainer.bind<int>().toResolver(_makeResolver(expectedValue));
expect(container.resolve<int>(), expectedValue);
});
test("Container resolve() returns a several value from parent container",
() {
final expectedIntValue = 5;
final expectedStringValue = "Hello world";
final parentContainer = DiContainer();
final container = DiContainer(parentContainer);
parentContainer.bind<int>().toResolver(_makeResolver(expectedIntValue));
parentContainer
.bind<String>()
.toResolver(_makeResolver(expectedStringValue));
expect(container.resolve<int>(), expectedIntValue);
expect(container.resolve<String>(), expectedStringValue);
});
test("Container resolve() throws a state error if parent hasn't value too",
() {
final parentContainer = DiContainer();
final container = DiContainer(parentContainer);
expect(() => container.resolve<int>(), throwsA(isA<StateError>()));
});
test("Container has() returns false if parent has a resolver", () {
final parentContainer = DiContainer();
final container = DiContainer(parentContainer);
parentContainer.bind<int>().toResolver(_makeResolver(5));
expect(container.has<int>(), false);
});
test("Container has() returns false if parent hasn't a resolver", () {
final parentContainer = DiContainer();
final container = DiContainer(parentContainer);
expect(container.has<int>(), false);
});
test("Container hasInTree() returns true if parent has a resolver", () {
final parentContainer = DiContainer();
final container = DiContainer(parentContainer);
parentContainer.bind<int>().toResolver(_makeResolver(5));
expect(container.hasInTree<int>(), true);
});
test("Test asSingelton", () {
final expectedIntValue = 10;
final containerA = DiContainer();
final containerB = DiContainer(containerA);
containerA.bind<int>().toValue(expectedIntValue).asSingleton();
expect(containerB.resolve<int>(), expectedIntValue);
});
test("Child container can resolve parent container's value", () {
final containerA = DiContainer();
final a = AA();
containerA.bind<A>().toValue(a);
final containerB = DiContainer(containerA);
final containerC = DiContainer(containerB);
expect(containerC.resolve<A>(), a);
});
});
test("Bind to the factory resolves with value", () {
final container = DiContainer();
final a = AA();
container.bind<A>().toFactory(() => a);
expect(container.resolve<A>(), a);
});
test("Bind to the factory resolves with value", () {
final container = DiContainer();
final a = AA();
container.bind<A>().toValue(a);
container.bind<DependOnA>().toFactory1<A>((a) => DependOnA(a));
expect(container.resolve<DependOnA>().a, a);
});
test("Bind to the factory resolves with 2 value", () {
final container = DiContainer();
final a = AA();
final b = BB();
container.bind<A>().toValue(a);
container.bind<B>().toValue(b);
container.bind<DependOnAB>().toFactory2<A, B>((a, b) => DependOnAB(a, b));
expect(container.resolve<DependOnAB>().a, a);
expect(container.resolve<DependOnAB>().b, b);
});
test("Bind to the factory resolves with 3 value", () {
final container = DiContainer();
final a = AA();
final b = BB();
final c = CC();
container.bind<A>().toValue(a);
container.bind<B>().toValue(b);
container.bind<C>().toValue(c);
container
.bind<DependOnABC>()
.toFactory3<A, B, C>((a, b, c) => DependOnABC(a, b, c));
expect(container.resolve<DependOnABC>().a, a);
expect(container.resolve<DependOnABC>().b, b);
expect(container.resolve<DependOnABC>().c, c);
});
test("Bind to the factory resolves with 4 value", () {
final container = DiContainer();
final a = AA();
final b = BB();
final c = CC();
final d = DD();
container.bind<A>().toValue(a);
container.bind<B>().toValue(b);
container.bind<C>().toValue(c);
container.bind<D>().toValue(d);
container
.bind<DependOnABCD>()
.toFactory4<A, B, C, D>((a, b, c, d) => DependOnABCD(a, b, c, d));
expect(container.resolve<DependOnABCD>().a, a);
expect(container.resolve<DependOnABCD>().b, b);
expect(container.resolve<DependOnABCD>().c, c);
expect(container.resolve<DependOnABCD>().d, d);
});
test("Bind to the factory resolves with 5 value", () {
final container = DiContainer();
final a = AA();
final b = BB();
final c = CC();
final d = DD();
final e = EE();
container.bind<A>().toValue(a);
container.bind<B>().toValue(b);
container.bind<C>().toValue(c);
container.bind<D>().toValue(d);
container.bind<E>().toValue(e);
container.bind<DependOnABCDE>().toFactory5<A, B, C, D, E>(
(a, b, c, d, e) => DependOnABCDE(a, b, c, d, e));
expect(container.resolve<DependOnABCDE>().a, a);
expect(container.resolve<DependOnABCDE>().b, b);
expect(container.resolve<DependOnABCDE>().c, c);
expect(container.resolve<DependOnABCDE>().d, d);
expect(container.resolve<DependOnABCDE>().e, e);
});
test("Bind to the factory resolves with 6 value", () {
final container = DiContainer();
final a = AA();
final b = BB();
final c = CC();
final d = DD();
final e = EE();
final f = FF();
container.bind<A>().toValue(a);
container.bind<B>().toValue(b);
container.bind<C>().toValue(c);
container.bind<D>().toValue(d);
container.bind<E>().toValue(e);
container.bind<F>().toValue(f);
container.bind<DependOnABCDEF>().toFactory6<A, B, C, D, E, F>(
(a, b, c, d, e, f) => DependOnABCDEF(a, b, c, d, e, f));
expect(container.resolve<DependOnABCDEF>().a, a);
expect(container.resolve<DependOnABCDEF>().b, b);
expect(container.resolve<DependOnABCDEF>().c, c);
expect(container.resolve<DependOnABCDEF>().d, d);
expect(container.resolve<DependOnABCDEF>().e, e);
expect(container.resolve<DependOnABCDEF>().f, f);
});
test("Bind to the factory resolves with 7 value", () {
final container = DiContainer();
final a = AA();
final b = BB();
final c = CC();
final d = DD();
final e = EE();
final f = FF();
final g = GG();
container.bind<A>().toValue(a);
container.bind<B>().toValue(b);
container.bind<C>().toValue(c);
container.bind<D>().toValue(d);
container.bind<E>().toValue(e);
container.bind<F>().toValue(f);
container.bind<G>().toValue(g);
container.bind<DependOnABCDEFG>().toFactory7<A, B, C, D, E, F, G>(
(a, b, c, d, e, f, g) => DependOnABCDEFG(a, b, c, d, e, f, g));
expect(container.resolve<DependOnABCDEFG>().a, a);
expect(container.resolve<DependOnABCDEFG>().b, b);
expect(container.resolve<DependOnABCDEFG>().c, c);
expect(container.resolve<DependOnABCDEFG>().d, d);
expect(container.resolve<DependOnABCDEFG>().e, e);
expect(container.resolve<DependOnABCDEFG>().f, f);
expect(container.resolve<DependOnABCDEFG>().g, g);
});
test("Bind to the factory resolves with 8 value", () {
final container = DiContainer();
final a = AA();
final b = BB();
final c = CC();
final d = DD();
final e = EE();
final f = FF();
final g = GG();
final h = HH();
container.bind<A>().toValue(a);
container.bind<B>().toValue(b);
container.bind<C>().toValue(c);
container.bind<D>().toValue(d);
container.bind<E>().toValue(e);
container.bind<F>().toValue(f);
container.bind<G>().toValue(g);
container.bind<H>().toValue(h);
container.bind<DependOnABCDEFGH>().toFactory8<A, B, C, D, E, F, G, H>(
(a, b, c, d, e, f, g, h) => DependOnABCDEFGH(a, b, c, d, e, f, g, h));
expect(container.resolve<DependOnABCDEFGH>().a, a);
expect(container.resolve<DependOnABCDEFGH>().b, b);
expect(container.resolve<DependOnABCDEFGH>().c, c);
expect(container.resolve<DependOnABCDEFGH>().d, d);
expect(container.resolve<DependOnABCDEFGH>().e, e);
expect(container.resolve<DependOnABCDEFGH>().f, f);
expect(container.resolve<DependOnABCDEFGH>().g, g);
expect(container.resolve<DependOnABCDEFGH>().h, h);
});
}
ResolverMock<T> _makeResolver<T>(T expectedValue) {
final resolverMock = new ResolverMock<T>();
when(resolverMock.resolve()).thenReturn(expectedValue);
return resolverMock;
}
class ResolverMock<T> extends Mock implements Resolver<T> {}
abstract class A {}
class AA implements A {}
abstract class B {}
class BB implements B {}
abstract class C {}
class CC implements C {}
abstract class D {}
class DD implements D {}
abstract class E {}
class EE implements E {}
abstract class F {}
class FF implements F {}
abstract class G {}
class GG implements G {}
abstract class H {}
class HH implements H {}
class DependOnA {
final A a;
DependOnA(this.a) : assert(a != null);
}
class DependOnAB {
final A a;
final B b;
DependOnAB(this.a, this.b) : assert(a != null && b != null);
}
class DependOnABC {
final A a;
final B b;
final C c;
DependOnABC(this.a, this.b, this.c)
: assert(a != null && b != null && c != null);
}
class DependOnABCD {
final A a;
final B b;
final C c;
final D d;
DependOnABCD(this.a, this.b, this.c, this.d)
: assert(a != null && b != null && c != null && d != null);
}
class DependOnABCDE {
final A a;
final B b;
final C c;
final D d;
final E e;
DependOnABCDE(this.a, this.b, this.c, this.d, this.e)
: assert(a != null && b != null && c != null && d != null && e != null);
}
class DependOnABCDEF {
final A a;
final B b;
final C c;
final D d;
final E e;
final F f;
DependOnABCDEF(this.a, this.b, this.c, this.d, this.e, this.f)
: assert(a != null &&
b != null &&
c != null &&
d != null &&
e != null &&
f != null);
}
class DependOnABCDEFG {
final A a;
final B b;
final C c;
final D d;
final E e;
final F f;
final G g;
DependOnABCDEFG(this.a, this.b, this.c, this.d, this.e, this.f, this.g)
: assert(a != null &&
b != null &&
c != null &&
d != null &&
e != null &&
f != null &&
g != null);
}
class DependOnABCDEFGH {
final A a;
final B b;
final C c;
final D d;
final E e;
final F f;
final G g;
final H h;
DependOnABCDEFGH(
this.a, this.b, this.c, this.d, this.e, this.f, this.g, this.h)
: assert(a != null &&
b != null &&
c != null &&
d != null &&
e != null &&
f != null &&
g != null &&
h != null);
}

View File

@@ -0,0 +1,27 @@
import 'package:dart_di/resolvers/factory_resolver.dart';
import 'package:test/test.dart';
import 'package:mockito/mockito.dart' as mockito;
void main() {
test('Factory resolver resolves with factory', () {
const expected = 3;
final factoryResolver = new FactoryResolver(() => expected);
expect(factoryResolver.resolve(), expected);
});
test('Factory creates value only after resolve() call', () {
final spy = new SpyMock();
final factoryResolver = new FactoryResolver(() => spy.onFactory());
mockito.verifyNever(spy.onFactory());
factoryResolver.resolve();
mockito.verify(spy.onFactory());
});
}
abstract class Spy {
void onFactory();
}
class SpyMock extends mockito.Mock implements Spy {}

View File

@@ -0,0 +1,36 @@
import 'package:dart_di/resolvers/factory_resolver.dart';
import 'package:dart_di/resolvers/singelton_resolver.dart';
import 'package:test/test.dart';
import 'package:mockito/mockito.dart' as mockito;
void main() {
test(
'Not singleton resolver resolves different values after multiple resolve() calls',
() {
const callCount = 3;
final spy = new SpyMock();
final factoryResolver = new FactoryResolver(() => spy..onFactory());
for (var i = 0; i < callCount; i++) factoryResolver.resolve();
mockito.verify(spy.onFactory()).called(callCount);
});
test('Singleton resolver resolves same value after multiple resolve() calls',
() {
const callCount = 3;
final spy = new SpyMock();
final singletonResolver =
new SingletonResolver(new FactoryResolver(() => spy..onFactory()));
for (var i = 0; i < callCount; i++) singletonResolver.resolve();
mockito.verify(spy.onFactory()).called(1);
});
}
abstract class Spy {
void onFactory();
}
class SpyMock extends mockito.Mock implements Spy {}

View File

@@ -0,0 +1,11 @@
import 'package:dart_di/resolvers/value_resolver.dart';
import 'package:test/test.dart';
void main() {
test('Value resolver resolves with selected value', () {
var a = 3;
final valResolver = new ValueResolver(a);
expect(valResolver.resolve(), a);
});
}

View File

@@ -1,296 +0,0 @@
import 'package:cherrypick/src/binding.dart';
import 'package:test/test.dart';
void main() {
group('Check instance.', () {
group('Without name.', () {
test('Binding resolves null', () {
final binding = Binding<int>();
expect(binding.instance, null);
});
test('Binding check mode', () {
final expectedValue = 5;
final binding = Binding<int>().toInstance(expectedValue);
expect(binding.mode, Mode.INSTANCE);
});
test('Binding check singleton', () {
final expectedValue = 5;
final binding = Binding<int>().toInstance(expectedValue);
expect(binding.isSingleton, true);
});
test('Binding check value', () {
final expectedValue = 5;
final binding = Binding<int>().toInstance(expectedValue);
expect(binding.instance, expectedValue);
});
test('Binding resolves value', () {
final expectedValue = 5;
final binding = Binding<int>().toInstance(expectedValue);
expect(binding.instance, expectedValue);
});
});
group('With name.', () {
test('Binding resolves null', () {
final binding = Binding<int>().withName('expectedValue');
expect(binding.instance, null);
});
test('Binding check mode', () {
final expectedValue = 5;
final binding =
Binding<int>().withName('expectedValue').toInstance(expectedValue);
expect(binding.mode, Mode.INSTANCE);
});
test('Binding check key', () {
final expectedValue = 5;
final binding =
Binding<int>().withName('expectedValue').toInstance(expectedValue);
expect(binding.key, int);
});
test('Binding check singleton', () {
final expectedValue = 5;
final binding =
Binding<int>().withName('expectedValue').toInstance(expectedValue);
expect(binding.isSingleton, true);
});
test('Binding check value', () {
final expectedValue = 5;
final binding =
Binding<int>().withName('expectedValue').toInstance(expectedValue);
expect(binding.instance, expectedValue);
});
test('Binding check value', () {
final expectedValue = 5;
final binding =
Binding<int>().withName('expectedValue').toInstance(expectedValue);
expect(binding.name, 'expectedValue');
});
test('Binding resolves value', () {
final expectedValue = 5;
final binding =
Binding<int>().withName('expectedValue').toInstance(expectedValue);
expect(binding.instance, expectedValue);
});
});
});
group('Check provide.', () {
group('Without name.', () {
test('Binding resolves null', () {
final binding = Binding<int>();
expect(binding.provider, null);
});
test('Binding check mode', () {
final expectedValue = 5;
final binding = Binding<int>().toProvide(() => expectedValue);
expect(binding.mode, Mode.PROVIDER_INSTANCE);
});
test('Binding check singleton', () {
final expectedValue = 5;
final binding = Binding<int>().toProvide(() => expectedValue);
expect(binding.isSingleton, false);
});
test('Binding check value', () {
final expectedValue = 5;
final binding = Binding<int>().toProvide(() => expectedValue);
expect(binding.provider, expectedValue);
});
test('Binding resolves value', () {
final expectedValue = 5;
final binding = Binding<int>().toProvide(() => expectedValue);
expect(binding.provider, expectedValue);
});
});
group('With name.', () {
test('Binding resolves null', () {
final binding = Binding<int>().withName('expectedValue');
expect(binding.provider, null);
});
test('Binding check mode', () {
final expectedValue = 5;
final binding = Binding<int>()
.withName('expectedValue')
.toProvide(() => expectedValue);
expect(binding.mode, Mode.PROVIDER_INSTANCE);
});
test('Binding check key', () {
final expectedValue = 5;
final binding = Binding<int>()
.withName('expectedValue')
.toProvide(() => expectedValue);
expect(binding.key, int);
});
test('Binding check singleton', () {
final expectedValue = 5;
final binding = Binding<int>()
.withName('expectedValue')
.toProvide(() => expectedValue);
expect(binding.isSingleton, false);
});
test('Binding check value', () {
final expectedValue = 5;
final binding = Binding<int>()
.withName('expectedValue')
.toProvide(() => expectedValue);
expect(binding.provider, expectedValue);
});
test('Binding check value', () {
final expectedValue = 5;
final binding = Binding<int>()
.withName('expectedValue')
.toProvide(() => expectedValue);
expect(binding.name, 'expectedValue');
});
test('Binding resolves value', () {
final expectedValue = 5;
final binding = Binding<int>()
.withName('expectedValue')
.toProvide(() => expectedValue);
expect(binding.provider, expectedValue);
});
});
});
group('Check singleton provide.', () {
group('Without name.', () {
test('Binding resolves null', () {
final binding = Binding<int>().singleton();
expect(binding.provider, null);
});
test('Binding check mode', () {
final expectedValue = 5;
final binding =
Binding<int>().toProvide(() => expectedValue).singleton();
expect(binding.mode, Mode.PROVIDER_INSTANCE);
});
test('Binding check singleton', () {
final expectedValue = 5;
final binding =
Binding<int>().toProvide(() => expectedValue).singleton();
expect(binding.isSingleton, true);
});
test('Binding check value', () {
final expectedValue = 5;
final binding =
Binding<int>().toProvide(() => expectedValue).singleton();
expect(binding.provider, expectedValue);
});
test('Binding resolves value', () {
final expectedValue = 5;
final binding =
Binding<int>().toProvide(() => expectedValue).singleton();
expect(binding.provider, expectedValue);
});
});
group('With name.', () {
test('Binding resolves null', () {
final binding = Binding<int>().withName('expectedValue').singleton();
expect(binding.provider, null);
});
test('Binding check mode', () {
final expectedValue = 5;
final binding = Binding<int>()
.withName('expectedValue')
.toProvide(() => expectedValue)
.singleton();
expect(binding.mode, Mode.PROVIDER_INSTANCE);
});
test('Binding check key', () {
final expectedValue = 5;
final binding = Binding<int>()
.withName('expectedValue')
.toProvide(() => expectedValue)
.singleton();
expect(binding.key, int);
});
test('Binding check singleton', () {
final expectedValue = 5;
final binding = Binding<int>()
.withName('expectedValue')
.toProvide(() => expectedValue)
.singleton();
expect(binding.isSingleton, true);
});
test('Binding check value', () {
final expectedValue = 5;
final binding = Binding<int>()
.withName('expectedValue')
.toProvide(() => expectedValue)
.singleton();
expect(binding.provider, expectedValue);
});
test('Binding check value', () {
final expectedValue = 5;
final binding = Binding<int>()
.withName('expectedValue')
.toProvide(() => expectedValue)
.singleton();
expect(binding.name, 'expectedValue');
});
test('Binding resolves value', () {
final expectedValue = 5;
final binding = Binding<int>()
.withName('expectedValue')
.toProvide(() => expectedValue)
.singleton();
expect(binding.provider, expectedValue);
});
});
});
}

View File

@@ -1,91 +0,0 @@
import 'package:cherrypick/src/module.dart';
import 'package:cherrypick/src/scope.dart';
import 'package:test/test.dart';
void main() {
group('Without parent scope.', () {
test('Parent scope is null.', () {
final scope = Scope(null);
expect(scope.parentScope, null);
});
test('Open sub scope.', () {
final scope = Scope(null);
final subScope = scope.openSubScope('subScope');
expect(scope.openSubScope('subScope'), subScope);
});
test("Container throws state error if the value can't be resolved", () {
final scope = Scope(null);
expect(() => scope.resolve<String>(), throwsA(isA<StateError>()));
});
test('Container resolves value after adding a dependency', () {
final expectedValue = 'test string';
final scope = Scope(null)
.installModules([TestModule<String>(value: expectedValue)]);
expect(scope.resolve<String>(), expectedValue);
});
});
group('With parent scope.', () {
/*
test(
"Container bind() throws state error (if it's parent already has a resolver)",
() {
final parentScope = new Scope(null)
.installModules([TestModule<String>(value: "string one")]);
final scope = new Scope(parentScope);
expect(
() => scope.installModules([TestModule<String>(value: "string two")]),
throwsA(isA<StateError>()));
});
*/
test('Container resolve() returns a value from parent container.', () {
final expectedValue = 5;
final parentScope = Scope(null);
final scope = Scope(parentScope);
parentScope.installModules([TestModule<int>(value: expectedValue)]);
expect(scope.resolve<int>(), expectedValue);
});
test('Container resolve() returns a several value from parent container.',
() {
final expectedIntValue = 5;
final expectedStringValue = 'Hello world';
final parentScope = Scope(null).installModules([
TestModule<int>(value: expectedIntValue),
TestModule<String>(value: expectedStringValue)
]);
final scope = Scope(parentScope);
expect(scope.resolve<int>(), expectedIntValue);
expect(scope.resolve<String>(), expectedStringValue);
});
test("Container resolve() throws a state error if parent hasn't value too.",
() {
final parentScope = Scope(null);
final scope = Scope(parentScope);
expect(() => scope.resolve<int>(), throwsA(isA<StateError>()));
});
});
}
class TestModule<T> extends Module {
final T value;
final String? name;
TestModule({required this.value, this.name});
@override
void builder(Scope currentScope) {
if (name == null) {
bind<T>().toInstance(value);
} else {
bind<T>().withName(name!).toInstance(value);
}
}
}