mirror of
https://github.com/pese-git/cherrypick.git
synced 2026-01-24 13:47:24 +00:00
Compare commits
5 Commits
cherrypick
...
cherrypick
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
58daf668c5 | ||
|
|
b57ca797e1 | ||
|
|
38fd356ec3 | ||
|
|
8fd18df811 | ||
|
|
06c0dd60c0 |
59
CHANGELOG.md
59
CHANGELOG.md
@@ -3,65 +3,6 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
## 2025-08-04
|
|
||||||
|
|
||||||
### Changes
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Packages with breaking changes:
|
|
||||||
|
|
||||||
- [`cherrypick` - `v3.0.0-dev.2`](#cherrypick---v300-dev2)
|
|
||||||
|
|
||||||
Packages with other changes:
|
|
||||||
|
|
||||||
- [`cherrypick_flutter` - `v1.1.3-dev.2`](#cherrypick_flutter---v113-dev2)
|
|
||||||
|
|
||||||
Packages with dependency updates only:
|
|
||||||
|
|
||||||
> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project.
|
|
||||||
|
|
||||||
- `cherrypick_flutter` - `v1.1.3-dev.2`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### `cherrypick` - `v3.0.0-dev.2`
|
|
||||||
|
|
||||||
- **FEAT**(binding): add deprecated proxy async methods for backward compatibility and highlight transition to modern API.
|
|
||||||
- **DOCS**: add quick guide for circular dependency detection to README.
|
|
||||||
- **DOCS**: add quick guide for circular dependency detection to README.
|
|
||||||
- **BREAKING** **FEAT**: implement comprehensive circular dependency detection system.
|
|
||||||
- **BREAKING** **FEAT**: implement comprehensive circular dependency detection system.
|
|
||||||
|
|
||||||
|
|
||||||
## 2025-07-30
|
|
||||||
|
|
||||||
### Changes
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Packages with breaking changes:
|
|
||||||
|
|
||||||
- There are no breaking changes in this release.
|
|
||||||
|
|
||||||
Packages with other changes:
|
|
||||||
|
|
||||||
- [`cherrypick` - `v3.0.0-dev.1`](#cherrypick---v300-dev1)
|
|
||||||
- [`cherrypick_flutter` - `v1.1.3-dev.1`](#cherrypick_flutter---v113-dev1)
|
|
||||||
|
|
||||||
Packages with dependency updates only:
|
|
||||||
|
|
||||||
> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project.
|
|
||||||
|
|
||||||
- `cherrypick_flutter` - `v1.1.3-dev.1`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### `cherrypick` - `v3.0.0-dev.1`
|
|
||||||
|
|
||||||
- **DOCS**: add quick guide for circular dependency detection to README.
|
|
||||||
|
|
||||||
|
|
||||||
## 2025-07-30
|
## 2025-07-30
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
# benchmark_cherrypick
|
|
||||||
|
|
||||||
Benchmarks for performance and features of the cherrypick (core) DI container.
|
|
||||||
|
|
||||||
All scenarios use the public API capabilities of cherrypick (scope, module, binding, scoping, and async support).
|
|
||||||
|
|
||||||
## Scenarios
|
|
||||||
|
|
||||||
- **RegisterAndResolve**: basic registration and resolution of a dependency.
|
|
||||||
- **ChainSingleton (A->B->C, singleton)**: dependency chain, all as singletons.
|
|
||||||
- **ChainFactory (A->B->C, factory)**: dependency chain with factory bindings (new instance on each request).
|
|
||||||
- **NamedResolve (by name)**: resolving a named dependency among multiple implementations.
|
|
||||||
- **AsyncChain (A->B->C, async)**: asynchronous dependency chain.
|
|
||||||
- **ScopeOverride (child overrides parent)**: overriding a dependency in a child scope over a parent.
|
|
||||||
|
|
||||||
## Benchmark results
|
|
||||||
|
|
||||||
| Scenario | RunTime (μs) |
|
|
||||||
|----------------------------------------------------|---------------|
|
|
||||||
| RegisterAndResolve | 0.4574 |
|
|
||||||
| ChainSingleton (A->B->C, singleton) | 0.3759 |
|
|
||||||
| ChainFactory (A->B->C, factory) | 1.3783 |
|
|
||||||
| NamedResolve (by name) | 0.5193 |
|
|
||||||
| AsyncChain (A->B->C, async) | 0.5985 |
|
|
||||||
| ScopeOverride (child overrides parent) | 0.3611 |
|
|
||||||
|
|
||||||
## How to run
|
|
||||||
|
|
||||||
1. Get dependencies:
|
|
||||||
```shell
|
|
||||||
dart pub get
|
|
||||||
```
|
|
||||||
2. Run the benchmarks:
|
|
||||||
```shell
|
|
||||||
dart run bin/main.dart
|
|
||||||
```
|
|
||||||
|
|
||||||
A text report with all metrics will be displayed in the console.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
To add your custom scenario — just create a new Dart file and declare a class extending BenchmarkBase or AsyncBenchmarkBase, then add its invocation to main.dart.
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
# benchmark_cherrypick
|
|
||||||
|
|
||||||
Бенчмарки производительности и функциональности DI-контейнера cherrypick (core).
|
|
||||||
|
|
||||||
Все сценарии используют реальные возможности public API cherrypick (scope, module, binding, scoping и асинхронность).
|
|
||||||
|
|
||||||
## Сценарии
|
|
||||||
|
|
||||||
- **RegisterAndResolve**: базовая операция регистрации и разрешения зависимости.
|
|
||||||
- **ChainSingleton (A->B->C, singleton)**: цепочка зависимостей, все singletons.
|
|
||||||
- **ChainFactory (A->B->C, factory)**: цепочка зависимостей с factory биндингами, новые объекты на каждый запрос.
|
|
||||||
- **NamedResolve (by name)**: разрешение именованной зависимости среди нескольких реализаций.
|
|
||||||
- **AsyncChain (A->B->C, async)**: асинхронная цепочка зависимостей.
|
|
||||||
- **ScopeOverride (child overrides parent)**: переопределение зависимости в дочернем scope над родительским.
|
|
||||||
|
|
||||||
## Результаты исследования
|
|
||||||
|
|
||||||
| Сценарий | RunTime (мкс) |
|
|
||||||
|----------------------------------------------------|--------------|
|
|
||||||
| RegisterAndResolve | 0.4574 |
|
|
||||||
| ChainSingleton (A->B->C, singleton) | 0.3759 |
|
|
||||||
| ChainFactory (A->B->C, factory) | 1.3783 |
|
|
||||||
| NamedResolve (by name) | 0.5193 |
|
|
||||||
| AsyncChain (A->B->C, async) | 0.5985 |
|
|
||||||
| ScopeOverride (child overrides parent) | 0.3611 |
|
|
||||||
|
|
||||||
## Как запускать
|
|
||||||
|
|
||||||
1. Получить зависимости:
|
|
||||||
```shell
|
|
||||||
dart pub get
|
|
||||||
```
|
|
||||||
2. Запустить бенчмарк:
|
|
||||||
```shell
|
|
||||||
dart run bin/main.dart
|
|
||||||
```
|
|
||||||
|
|
||||||
Будет показан текстовый отчёт по всем метрикам.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Если хотите добавить свой сценарий — создайте отдельный Dart-файл и объявите новый BenchmarkBase/AsyncBenchmarkBase, не забудьте вставить его вызов в main.
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
# This file configures the static analysis results for your project (errors,
|
|
||||||
# warnings, and lints).
|
|
||||||
#
|
|
||||||
# This enables the 'recommended' set of lints from `package:lints`.
|
|
||||||
# This set helps identify many issues that may lead to problems when running
|
|
||||||
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
|
|
||||||
# style and format.
|
|
||||||
#
|
|
||||||
# If you want a smaller set of lints you can change this to specify
|
|
||||||
# 'package:lints/core.yaml'. These are just the most critical lints
|
|
||||||
# (the recommended set includes the core lints).
|
|
||||||
# The core lints are also what is used by pub.dev for scoring packages.
|
|
||||||
|
|
||||||
include: package:lints/recommended.yaml
|
|
||||||
analyzer:
|
|
||||||
errors:
|
|
||||||
deprecated_member_use: ignore
|
|
||||||
|
|
||||||
# Uncomment the following section to specify additional rules.
|
|
||||||
|
|
||||||
# linter:
|
|
||||||
# rules:
|
|
||||||
# - camel_case_types
|
|
||||||
|
|
||||||
# analyzer:
|
|
||||||
# exclude:
|
|
||||||
# - path/to/excluded/files/**
|
|
||||||
|
|
||||||
# For more information about the core and recommended set of lints, see
|
|
||||||
# https://dart.dev/go/core-lints
|
|
||||||
|
|
||||||
# For additional information about configuring this file, see
|
|
||||||
# https://dart.dev/guides/language/analysis-options
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import 'package:benchmark_cherrypick/cherrypick_benchmark.dart';
|
|
||||||
import 'package:benchmark_cherrypick/complex_bindings_benchmark.dart';
|
|
||||||
import 'package:benchmark_cherrypick/async_chain_benchmark.dart';
|
|
||||||
import 'package:benchmark_cherrypick/scope_override_benchmark.dart';
|
|
||||||
|
|
||||||
void main(List<String> args) async {
|
|
||||||
// Синхронные бенчмарки
|
|
||||||
RegisterAndResolveBenchmark().report();
|
|
||||||
ChainSingletonBenchmark().report();
|
|
||||||
ChainFactoryBenchmark().report();
|
|
||||||
NamedResolveBenchmark().report();
|
|
||||||
|
|
||||||
// Асинхронный бенчмарк
|
|
||||||
await AsyncChainBenchmark().report();
|
|
||||||
|
|
||||||
ScopeOverrideBenchmark().report();
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
// ignore: depend_on_referenced_packages
|
|
||||||
import 'package:benchmark_harness/benchmark_harness.dart';
|
|
||||||
import 'package:cherrypick/cherrypick.dart';
|
|
||||||
|
|
||||||
class AsyncA {}
|
|
||||||
class AsyncB {
|
|
||||||
final AsyncA a;
|
|
||||||
AsyncB(this.a);
|
|
||||||
}
|
|
||||||
class AsyncC {
|
|
||||||
final AsyncB b;
|
|
||||||
AsyncC(this.b);
|
|
||||||
}
|
|
||||||
|
|
||||||
class AsyncChainModule extends Module {
|
|
||||||
@override
|
|
||||||
void builder(Scope currentScope) {
|
|
||||||
bind<AsyncA>().toProvideAsync(() async => AsyncA()).singleton();
|
|
||||||
bind<AsyncB>().toProvideAsync(() async => AsyncB(await currentScope.resolveAsync<AsyncA>())).singleton();
|
|
||||||
bind<AsyncC>().toProvideAsync(() async => AsyncC(await currentScope.resolveAsync<AsyncB>())).singleton();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class AsyncChainBenchmark extends AsyncBenchmarkBase {
|
|
||||||
AsyncChainBenchmark() : super('AsyncChain (A->B->C, async)');
|
|
||||||
late Scope scope;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> setup() async {
|
|
||||||
CherryPick.disableGlobalCycleDetection();
|
|
||||||
CherryPick.disableGlobalCrossScopeCycleDetection();
|
|
||||||
scope = CherryPick.openRootScope();
|
|
||||||
scope.installModules([AsyncChainModule()]);
|
|
||||||
}
|
|
||||||
@override
|
|
||||||
Future<void> teardown() async {
|
|
||||||
CherryPick.closeRootScope();
|
|
||||||
}
|
|
||||||
@override
|
|
||||||
Future<void> run() async {
|
|
||||||
await scope.resolveAsync<AsyncC>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
// ignore: depend_on_referenced_packages
|
|
||||||
import 'package:benchmark_harness/benchmark_harness.dart';
|
|
||||||
import 'package:cherrypick/cherrypick.dart';
|
|
||||||
|
|
||||||
class AppModule extends Module {
|
|
||||||
@override
|
|
||||||
void builder(Scope currentScope) {
|
|
||||||
bind<FooService>().toProvide(() => FooService());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dummy service for DI
|
|
||||||
class FooService {}
|
|
||||||
|
|
||||||
class RegisterAndResolveBenchmark extends BenchmarkBase {
|
|
||||||
RegisterAndResolveBenchmark() : super('RegisterAndResolve');
|
|
||||||
late final Scope scope;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void setup() {
|
|
||||||
CherryPick.disableGlobalCycleDetection();
|
|
||||||
CherryPick.disableGlobalCrossScopeCycleDetection();
|
|
||||||
scope = CherryPick.openRootScope();
|
|
||||||
scope.installModules([AppModule()]);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void run() {
|
|
||||||
scope.resolve<FooService>();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void teardown() => CherryPick.closeRootScope();
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
RegisterAndResolveBenchmark().report();
|
|
||||||
}
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
// ignore: depend_on_referenced_packages
|
|
||||||
import 'package:benchmark_harness/benchmark_harness.dart';
|
|
||||||
import 'package:cherrypick/cherrypick.dart';
|
|
||||||
|
|
||||||
// === DI graph: A -> B -> C (singleton) ===
|
|
||||||
class ServiceA {}
|
|
||||||
class ServiceB {
|
|
||||||
final ServiceA a;
|
|
||||||
ServiceB(this.a);
|
|
||||||
}
|
|
||||||
class ServiceC {
|
|
||||||
final ServiceB b;
|
|
||||||
ServiceC(this.b);
|
|
||||||
}
|
|
||||||
|
|
||||||
class ChainSingletonModule extends Module {
|
|
||||||
@override
|
|
||||||
void builder(Scope currentScope) {
|
|
||||||
bind<ServiceA>().toProvide(() => ServiceA()).singleton();
|
|
||||||
bind<ServiceB>().toProvide((() => ServiceB(currentScope.resolve<ServiceA>()))).singleton();
|
|
||||||
bind<ServiceC>().toProvide((() => ServiceC(currentScope.resolve<ServiceB>()))).singleton();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ChainSingletonBenchmark extends BenchmarkBase {
|
|
||||||
ChainSingletonBenchmark() : super('ChainSingleton (A->B->C, singleton)');
|
|
||||||
late Scope scope;
|
|
||||||
@override
|
|
||||||
void setup() {
|
|
||||||
scope = CherryPick.openRootScope();
|
|
||||||
scope.installModules([ChainSingletonModule()]);
|
|
||||||
}
|
|
||||||
@override
|
|
||||||
void teardown() => CherryPick.closeRootScope();
|
|
||||||
@override
|
|
||||||
void run() {
|
|
||||||
scope.resolve<ServiceC>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// === DI graph: A -> B -> C (factory/no singleton) ===
|
|
||||||
class ChainFactoryModule extends Module {
|
|
||||||
@override
|
|
||||||
void builder(Scope currentScope) {
|
|
||||||
bind<ServiceA>().toProvide(() => ServiceA());
|
|
||||||
bind<ServiceB>().toProvide((() => ServiceB(currentScope.resolve<ServiceA>())));
|
|
||||||
bind<ServiceC>().toProvide((() => ServiceC(currentScope.resolve<ServiceB>())));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ChainFactoryBenchmark extends BenchmarkBase {
|
|
||||||
ChainFactoryBenchmark() : super('ChainFactory (A->B->C, factory)');
|
|
||||||
late Scope scope;
|
|
||||||
@override
|
|
||||||
void setup() {
|
|
||||||
CherryPick.disableGlobalCycleDetection();
|
|
||||||
CherryPick.disableGlobalCrossScopeCycleDetection();
|
|
||||||
scope = CherryPick.openRootScope();
|
|
||||||
scope.installModules([ChainFactoryModule()]);
|
|
||||||
}
|
|
||||||
@override
|
|
||||||
void teardown() => CherryPick.closeRootScope();
|
|
||||||
@override
|
|
||||||
void run() {
|
|
||||||
scope.resolve<ServiceC>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// === Named bindings: Multiple implementations ===
|
|
||||||
class Impl1 {}
|
|
||||||
class Impl2 {}
|
|
||||||
class NamedModule extends Module {
|
|
||||||
@override
|
|
||||||
void builder(Scope currentScope) {
|
|
||||||
bind<Object>().toProvide(() => Impl1()).withName('impl1');
|
|
||||||
bind<Object>().toProvide(() => Impl2()).withName('impl2');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class NamedResolveBenchmark extends BenchmarkBase {
|
|
||||||
NamedResolveBenchmark() : super('NamedResolve (by name)');
|
|
||||||
late Scope scope;
|
|
||||||
@override
|
|
||||||
void setup() {
|
|
||||||
scope = CherryPick.openRootScope();
|
|
||||||
scope.installModules([NamedModule()]);
|
|
||||||
}
|
|
||||||
@override
|
|
||||||
void teardown() => CherryPick.closeRootScope();
|
|
||||||
@override
|
|
||||||
void run() {
|
|
||||||
// Switch name for comparison
|
|
||||||
scope.resolve<Object>(named: 'impl2');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
// ignore: depend_on_referenced_packages
|
|
||||||
import 'package:benchmark_harness/benchmark_harness.dart';
|
|
||||||
import 'package:cherrypick/cherrypick.dart';
|
|
||||||
|
|
||||||
class Shared {}
|
|
||||||
class ParentImpl extends Shared {}
|
|
||||||
class ChildImpl extends Shared {}
|
|
||||||
|
|
||||||
class ParentModule extends Module {
|
|
||||||
@override
|
|
||||||
void builder(Scope currentScope) {
|
|
||||||
bind<Shared>().toProvide(() => ParentImpl()).singleton();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ChildOverrideModule extends Module {
|
|
||||||
@override
|
|
||||||
void builder(Scope currentScope) {
|
|
||||||
bind<Shared>().toProvide(() => ChildImpl()).singleton();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ScopeOverrideBenchmark extends BenchmarkBase {
|
|
||||||
ScopeOverrideBenchmark() : super('ScopeOverride (child overrides parent)');
|
|
||||||
late Scope parent;
|
|
||||||
late Scope child;
|
|
||||||
@override
|
|
||||||
void setup() {
|
|
||||||
CherryPick.disableGlobalCycleDetection();
|
|
||||||
CherryPick.disableGlobalCrossScopeCycleDetection();
|
|
||||||
parent = CherryPick.openRootScope();
|
|
||||||
parent.installModules([ParentModule()]);
|
|
||||||
child = parent.openSubScope('child');
|
|
||||||
child.installModules([ChildOverrideModule()]);
|
|
||||||
}
|
|
||||||
@override
|
|
||||||
void teardown() {
|
|
||||||
CherryPick.closeRootScope();
|
|
||||||
}
|
|
||||||
@override
|
|
||||||
void run() {
|
|
||||||
// Должен возвращать ChildImpl, а не ParentImpl
|
|
||||||
final resolved = child.resolve<Shared>();
|
|
||||||
assert(resolved is ChildImpl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<module type="WEB_MODULE" version="4">
|
|
||||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
|
||||||
<exclude-output />
|
|
||||||
<content url="file://$MODULE_DIR$">
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$" isTestSource="false" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/.dart_tool" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/.pub" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
|
||||||
</content>
|
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
|
||||||
<orderEntry type="library" name="Dart SDK" level="project" />
|
|
||||||
<orderEntry type="library" name="Dart Packages" level="project" />
|
|
||||||
</component>
|
|
||||||
</module>
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
# Generated by pub
|
|
||||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
|
||||||
packages:
|
|
||||||
ansi_modifier:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: ansi_modifier
|
|
||||||
sha256: "4b97c241f345e49c929bd56d0198b567b7dfcca7ec8d4f798745c9ced998684c"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.1.4"
|
|
||||||
benchmark_harness:
|
|
||||||
dependency: "direct dev"
|
|
||||||
description:
|
|
||||||
name: benchmark_harness
|
|
||||||
sha256: "83f65107165883ba8623eb822daacb23dcf9f795c66841de758c9dd7c5a0cf28"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.3.1"
|
|
||||||
benchmark_runner:
|
|
||||||
dependency: "direct dev"
|
|
||||||
description:
|
|
||||||
name: benchmark_runner
|
|
||||||
sha256: "7de181228eb74cb34507ded2260fe88b3b71e0aacfe0dfa794df49edaf041ca3"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.0.4"
|
|
||||||
cherrypick:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
path: "../cherrypick"
|
|
||||||
relative: true
|
|
||||||
source: path
|
|
||||||
version: "3.0.0-dev.1"
|
|
||||||
exception_templates:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: exception_templates
|
|
||||||
sha256: "517f7c770da690073663f867ee2057ae2f4ffb28edae9da9faa624aa29ac76eb"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.3.1"
|
|
||||||
lazy_memo:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: lazy_memo
|
|
||||||
sha256: dcb30b4184a6d767e1d779d74ce784d752d38313b8fb4bad6b659ae7af4bb34d
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.2.3"
|
|
||||||
lints:
|
|
||||||
dependency: "direct dev"
|
|
||||||
description:
|
|
||||||
name: lints
|
|
||||||
sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "5.1.1"
|
|
||||||
meta:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: meta
|
|
||||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.17.0"
|
|
||||||
sdks:
|
|
||||||
dart: ">=3.6.0 <4.0.0"
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
name: benchmark_cherrypick
|
|
||||||
version: 0.1.0
|
|
||||||
publish_to: none
|
|
||||||
description: Benchmark for cherrypick core DI library
|
|
||||||
|
|
||||||
environment:
|
|
||||||
sdk: '>=3.0.0 <4.0.0'
|
|
||||||
|
|
||||||
dependencies:
|
|
||||||
cherrypick:
|
|
||||||
path: ../cherrypick
|
|
||||||
|
|
||||||
dev_dependencies:
|
|
||||||
lints: ^5.0.0
|
|
||||||
benchmark_harness: ^2.2.2
|
|
||||||
benchmark_runner: ^0.0.2
|
|
||||||
@@ -1,17 +1,3 @@
|
|||||||
## 3.0.0-dev.2
|
|
||||||
|
|
||||||
> Note: This release has breaking changes.
|
|
||||||
|
|
||||||
- **FEAT**(binding): add deprecated proxy async methods for backward compatibility and highlight transition to modern API.
|
|
||||||
- **DOCS**: add quick guide for circular dependency detection to README.
|
|
||||||
- **DOCS**: add quick guide for circular dependency detection to README.
|
|
||||||
- **BREAKING** **FEAT**: implement comprehensive circular dependency detection system.
|
|
||||||
- **BREAKING** **FEAT**: implement comprehensive circular dependency detection system.
|
|
||||||
|
|
||||||
## 3.0.0-dev.1
|
|
||||||
|
|
||||||
- **DOCS**: add quick guide for circular dependency detection to README.
|
|
||||||
|
|
||||||
## 3.0.0-dev.0
|
## 3.0.0-dev.0
|
||||||
|
|
||||||
> Note: This release has breaking changes.
|
> Note: This release has breaking changes.
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
A **Binding** acts as a configuration for how to create or provide a particular dependency. Bindings support:
|
A **Binding** acts as a configuration for how to create or provide a particular dependency. Bindings support:
|
||||||
|
|
||||||
|
|
||||||
- Direct instance assignment (`toInstance()`, `toInstanceAsync()`)
|
- Direct instance assignment (`toInstance()`, `toInstanceAsync()`)
|
||||||
- Lazy providers (sync/async functions)
|
- Lazy providers (sync/async functions)
|
||||||
- Provider functions supporting dynamic parameters
|
- Provider functions supporting dynamic parameters
|
||||||
@@ -238,74 +239,6 @@ class ApiClientImpl implements ApiClient {
|
|||||||
- [x] Null-safe Resolution (tryResolve/tryResolveAsync)
|
- [x] Null-safe Resolution (tryResolve/tryResolveAsync)
|
||||||
- [x] Circular Dependency Detection (Local and Global)
|
- [x] Circular Dependency Detection (Local and Global)
|
||||||
|
|
||||||
## Quick Guide: Circular Dependency Detection
|
|
||||||
|
|
||||||
CherryPick can detect circular dependencies in your DI configuration, helping you avoid infinite loops and hard-to-debug errors.
|
|
||||||
|
|
||||||
**How to use:**
|
|
||||||
|
|
||||||
### 1. Enable Cycle Detection for Development
|
|
||||||
|
|
||||||
**Local detection (within one scope):**
|
|
||||||
```dart
|
|
||||||
final scope = CherryPick.openSafeRootScope(); // Local detection enabled by default
|
|
||||||
// or, for an existing scope:
|
|
||||||
scope.enableCycleDetection();
|
|
||||||
```
|
|
||||||
|
|
||||||
**Global detection (across all scopes):**
|
|
||||||
```dart
|
|
||||||
CherryPick.enableGlobalCrossScopeCycleDetection();
|
|
||||||
final rootScope = CherryPick.openGlobalSafeRootScope();
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Error Example
|
|
||||||
|
|
||||||
If you declare mutually dependent services:
|
|
||||||
```dart
|
|
||||||
class A { A(B b); }
|
|
||||||
class B { B(A a); }
|
|
||||||
|
|
||||||
scope.installModules([
|
|
||||||
Module((bind) {
|
|
||||||
bind<A>().to((s) => A(s.resolve<B>()));
|
|
||||||
bind<B>().to((s) => B(s.resolve<A>()));
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
scope.resolve<A>(); // Throws CircularDependencyException
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Typical Usage Pattern
|
|
||||||
|
|
||||||
- **Always enable detection** in debug and test environments for maximum safety.
|
|
||||||
- **Disable detection** in production for performance (after code is tested).
|
|
||||||
|
|
||||||
```dart
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
if (kDebugMode) {
|
|
||||||
CherryPick.enableGlobalCycleDetection();
|
|
||||||
CherryPick.enableGlobalCrossScopeCycleDetection();
|
|
||||||
}
|
|
||||||
runApp(MyApp());
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Handling and Debugging Errors
|
|
||||||
|
|
||||||
On detection, `CircularDependencyException` is thrown with a readable dependency chain:
|
|
||||||
```dart
|
|
||||||
try {
|
|
||||||
scope.resolve<MyService>();
|
|
||||||
} on CircularDependencyException catch (e) {
|
|
||||||
print('Dependency chain: ${e.dependencyChain}');
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**More details:** See [cycle_detection.en.md](doc/cycle_detection.en.md)
|
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
- [Circular Dependency Detection (English)](doc/cycle_detection.en.md)
|
- [Circular Dependency Detection (English)](doc/cycle_detection.en.md)
|
||||||
@@ -325,4 +258,4 @@ Licensed under the [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2
|
|||||||
|
|
||||||
## Links
|
## Links
|
||||||
|
|
||||||
- [GitHub Repository](https://github.com/pese-git/cherrypick)
|
- [GitHub Repository](https://github.com/pese-git/cherrypick)
|
||||||
@@ -17,28 +17,19 @@ class FeatureModule extends Module {
|
|||||||
@override
|
@override
|
||||||
void builder(Scope currentScope) {
|
void builder(Scope currentScope) {
|
||||||
// Using toProvideAsync for async initialization
|
// Using toProvideAsync for async initialization
|
||||||
bind<DataRepository>()
|
bind<DataRepository>().withName("networkRepo").toProvideAsync(() async {
|
||||||
.withName("networkRepo")
|
|
||||||
.toProvideWithParams((params) async {
|
|
||||||
print('REPO PARAMS: $params');
|
|
||||||
|
|
||||||
final client = await Future.delayed(
|
final client = await Future.delayed(
|
||||||
Duration(milliseconds: 1000),
|
Duration(milliseconds: 100),
|
||||||
() => currentScope.resolve<ApiClient>(
|
() => currentScope.resolve<ApiClient>(
|
||||||
named: isMock ? "apiClientMock" : "apiClientImpl",
|
named: isMock ? "apiClientMock" : "apiClientImpl"));
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return NetworkDataRepository(client);
|
return NetworkDataRepository(client);
|
||||||
}).singleton();
|
}).singleton();
|
||||||
|
|
||||||
// Asynchronous initialization of DataBloc
|
// Asynchronous initialization of DataBloc
|
||||||
bind<DataBloc>().toProvide(
|
bind<DataBloc>().toProvideAsync(
|
||||||
() async {
|
() async {
|
||||||
final repo = await currentScope.resolveAsync<DataRepository>(
|
final repo = await currentScope.resolveAsync<DataRepository>(
|
||||||
named: "networkRepo",
|
named: "networkRepo");
|
||||||
params: 'Some params',
|
|
||||||
);
|
|
||||||
return DataBloc(repo);
|
return DataBloc(repo);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -47,19 +38,18 @@ class FeatureModule extends Module {
|
|||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
try {
|
try {
|
||||||
final scope = openRootScope().installModules([AppModule()]);
|
final scope = openRootScope().installModules([
|
||||||
|
AppModule(),
|
||||||
|
]);
|
||||||
|
|
||||||
final subScope = scope
|
final subScope = scope
|
||||||
.openSubScope("featureScope")
|
.openSubScope("featureScope")
|
||||||
.installModules([FeatureModule(isMock: true)]);
|
.installModules([FeatureModule(isMock: true)]);
|
||||||
|
|
||||||
// Asynchronous instance resolution
|
// Asynchronous instance resolution
|
||||||
final dataBloc = await subScope.resolveAsync<DataBloc>();
|
final dataBloc = await subScope.resolveAsync<DataBloc>();
|
||||||
dataBloc.data.listen(
|
dataBloc.data.listen((d) => print('Received data: $d'),
|
||||||
(d) => print('Received data: $d'),
|
onError: (e) => print('Error: $e'), onDone: () => print('DONE'));
|
||||||
onError: (e) => print('Error: $e'),
|
|
||||||
onDone: () => print('DONE'),
|
|
||||||
);
|
|
||||||
|
|
||||||
await dataBloc.fetchData();
|
await dataBloc.fetchData();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ library;
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
export 'package:cherrypick/src/binding_resolver.dart';
|
|
||||||
export 'package:cherrypick/src/binding.dart';
|
export 'package:cherrypick/src/binding.dart';
|
||||||
export 'package:cherrypick/src/cycle_detector.dart';
|
export 'package:cherrypick/src/cycle_detector.dart';
|
||||||
export 'package:cherrypick/src/global_cycle_detector.dart';
|
export 'package:cherrypick/src/global_cycle_detector.dart';
|
||||||
|
|||||||
@@ -11,21 +11,45 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import 'package:cherrypick/src/binding_resolver.dart';
|
enum Mode { simple, instance, providerInstance, providerInstanceWithParams }
|
||||||
|
|
||||||
|
typedef Provider<T> = T? Function();
|
||||||
|
|
||||||
|
typedef ProviderWithParams<T> = T Function(dynamic params);
|
||||||
|
|
||||||
|
typedef AsyncProvider<T> = Future<T> Function();
|
||||||
|
|
||||||
|
typedef AsyncProviderWithParams<T> = Future<T> Function(dynamic params);
|
||||||
|
|
||||||
/// RU: Класс Binding<T> настраивает параметры экземпляра.
|
/// RU: Класс Binding<T> настраивает параметры экземпляра.
|
||||||
/// ENG: The Binding<T> class configures the settings for the instance.
|
/// ENG: The Binding<T> class configures the settings for the instance.
|
||||||
///
|
///
|
||||||
class Binding<T> {
|
class Binding<T> {
|
||||||
|
late Mode _mode;
|
||||||
late Type _key;
|
late Type _key;
|
||||||
String? _name;
|
late String _name;
|
||||||
|
T? _instance;
|
||||||
|
Future<T>? _instanceAsync;
|
||||||
|
Provider<T>? _provider;
|
||||||
|
ProviderWithParams<T>? _providerWithParams;
|
||||||
|
|
||||||
BindingResolver<T>? _resolver;
|
AsyncProvider<T>? asyncProvider;
|
||||||
|
AsyncProviderWithParams<T>? asyncProviderWithParams;
|
||||||
|
|
||||||
|
late bool _isSingleton = false;
|
||||||
|
late bool _isNamed = false;
|
||||||
|
|
||||||
Binding() {
|
Binding() {
|
||||||
|
_mode = Mode.simple;
|
||||||
_key = T;
|
_key = T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// RU: Метод возвращает [Mode] экземпляра.
|
||||||
|
/// ENG: The method returns the [Mode] of the instance.
|
||||||
|
///
|
||||||
|
/// return [Mode]
|
||||||
|
Mode get mode => _mode;
|
||||||
|
|
||||||
/// RU: Метод возвращает тип экземпляра.
|
/// RU: Метод возвращает тип экземпляра.
|
||||||
/// ENG: The method returns the type of the instance.
|
/// ENG: The method returns the type of the instance.
|
||||||
///
|
///
|
||||||
@@ -36,21 +60,19 @@ class Binding<T> {
|
|||||||
/// ENG: The method returns the name of the instance.
|
/// ENG: The method returns the name of the instance.
|
||||||
///
|
///
|
||||||
/// return [String]
|
/// return [String]
|
||||||
String? get name => _name;
|
String get name => _name;
|
||||||
|
|
||||||
/// RU: Метод проверяет именован экземпляр или нет.
|
|
||||||
/// ENG: The method checks whether the instance is named or not.
|
|
||||||
///
|
|
||||||
/// return [bool]
|
|
||||||
bool get isNamed => _name != null;
|
|
||||||
|
|
||||||
/// RU: Метод проверяет сингелтон экземпляр или нет.
|
/// RU: Метод проверяет сингелтон экземпляр или нет.
|
||||||
/// ENG: The method checks the singleton instance or not.
|
/// ENG: The method checks the singleton instance or not.
|
||||||
///
|
///
|
||||||
/// return [bool]
|
/// return [bool]
|
||||||
bool get isSingleton => _resolver?.isSingleton ?? false;
|
bool get isSingleton => _isSingleton;
|
||||||
|
|
||||||
BindingResolver<T>? get resolver => _resolver;
|
/// RU: Метод проверяет именован экземпляр или нет.
|
||||||
|
/// ENG: The method checks whether the instance is named or not.
|
||||||
|
///
|
||||||
|
/// return [bool]
|
||||||
|
bool get isNamed => _isNamed;
|
||||||
|
|
||||||
/// RU: Добавляет имя для экземляпя [value].
|
/// RU: Добавляет имя для экземляпя [value].
|
||||||
/// ENG: Added name for instance [value].
|
/// ENG: Added name for instance [value].
|
||||||
@@ -58,6 +80,7 @@ class Binding<T> {
|
|||||||
/// return [Binding]
|
/// return [Binding]
|
||||||
Binding<T> withName(String name) {
|
Binding<T> withName(String name) {
|
||||||
_name = name;
|
_name = name;
|
||||||
|
_isNamed = true;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,9 +88,21 @@ class Binding<T> {
|
|||||||
/// ENG: Initialization instance [value].
|
/// ENG: Initialization instance [value].
|
||||||
///
|
///
|
||||||
/// return [Binding]
|
/// return [Binding]
|
||||||
Binding<T> toInstance(Instance<T> value) {
|
Binding<T> toInstance(T value) {
|
||||||
_resolver = InstanceResolver<T>(value);
|
_mode = Mode.instance;
|
||||||
|
_instance = value;
|
||||||
|
_isSingleton = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// RU: Инициализация экземляпяра [value].
|
||||||
|
/// ENG: Initialization instance [value].
|
||||||
|
///
|
||||||
|
/// return [Binding]
|
||||||
|
Binding<T> toInstanceAsync(Future<T> value) {
|
||||||
|
_mode = Mode.instance;
|
||||||
|
_instanceAsync = value;
|
||||||
|
_isSingleton = true;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,8 +111,18 @@ class Binding<T> {
|
|||||||
///
|
///
|
||||||
/// return [Binding]
|
/// return [Binding]
|
||||||
Binding<T> toProvide(Provider<T> value) {
|
Binding<T> toProvide(Provider<T> value) {
|
||||||
_resolver = ProviderResolver<T>((_) => value.call(), withParams: false);
|
_mode = Mode.providerInstance;
|
||||||
|
_provider = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// RU: Инициализация экземляпяра через провайдер [value].
|
||||||
|
/// ENG: Initialization instance via provider [value].
|
||||||
|
///
|
||||||
|
/// return [Binding]
|
||||||
|
Binding<T> toProvideAsync(AsyncProvider<T> provider) {
|
||||||
|
_mode = Mode.providerInstance;
|
||||||
|
asyncProvider = provider;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,24 +131,19 @@ class Binding<T> {
|
|||||||
///
|
///
|
||||||
/// return [Binding]
|
/// return [Binding]
|
||||||
Binding<T> toProvideWithParams(ProviderWithParams<T> value) {
|
Binding<T> toProvideWithParams(ProviderWithParams<T> value) {
|
||||||
_resolver = ProviderResolver<T>(value, withParams: true);
|
_mode = Mode.providerInstanceWithParams;
|
||||||
|
_providerWithParams = value;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated('Use toInstance instead of toInstanceAsync')
|
/// RU: Инициализация экземляра через асинхронный провайдер [value] с динамическим параметром.
|
||||||
Binding<T> toInstanceAsync(Instance<T> value) {
|
/// ENG: Initializes the instance via async provider [value] with a dynamic param.
|
||||||
return this.toInstance(value);
|
///
|
||||||
}
|
/// return [Binding]
|
||||||
|
Binding<T> toProvideAsyncWithParams(AsyncProviderWithParams<T> provider) {
|
||||||
@Deprecated('Use toProvide instead of toProvideAsync')
|
_mode = Mode.providerInstanceWithParams;
|
||||||
Binding<T> toProvideAsync(Provider<T> value) {
|
asyncProviderWithParams = provider;
|
||||||
return this.toProvide(value);
|
return this;
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated('Use toProvideWithParams instead of toProvideAsyncWithParams')
|
|
||||||
Binding<T> toProvideAsyncWithParams(ProviderWithParams<T> value) {
|
|
||||||
return this.toProvideWithParams(value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Инициализация экземляпяра как сингелтон [value].
|
/// RU: Инициализация экземляпяра как сингелтон [value].
|
||||||
@@ -111,16 +151,40 @@ class Binding<T> {
|
|||||||
///
|
///
|
||||||
/// return [Binding]
|
/// return [Binding]
|
||||||
Binding<T> singleton() {
|
Binding<T> singleton() {
|
||||||
_resolver?.toSingleton();
|
_isSingleton = true;
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
T? resolveSync([dynamic params]) {
|
/// RU: Поиск экземпляра.
|
||||||
return resolver?.resolveSync(params);
|
/// ENG: Resolve instance.
|
||||||
|
///
|
||||||
|
/// return [T]
|
||||||
|
T? get instance => _instance;
|
||||||
|
|
||||||
|
/// RU: Поиск экземпляра.
|
||||||
|
/// ENG: Resolve instance.
|
||||||
|
///
|
||||||
|
/// return [T]
|
||||||
|
Future<T>? get instanceAsync => _instanceAsync;
|
||||||
|
|
||||||
|
/// RU: Поиск экземпляра.
|
||||||
|
/// ENG: Resolve instance.
|
||||||
|
///
|
||||||
|
/// return [T]
|
||||||
|
T? get provider {
|
||||||
|
if (_isSingleton) {
|
||||||
|
_instance ??= _provider?.call();
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
return _provider?.call();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<T>? resolveAsync([dynamic params]) {
|
/// RU: Поиск экземпляра с параметром.
|
||||||
return resolver?.resolveAsync(params);
|
///
|
||||||
|
/// ENG: Resolve instance with [params].
|
||||||
|
///
|
||||||
|
/// return [T]
|
||||||
|
T? providerWithParams(dynamic params) {
|
||||||
|
return _providerWithParams?.call(params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,129 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
|
|
||||||
typedef Instance<T> = FutureOr<T>;
|
|
||||||
|
|
||||||
/// RU: Синхронный или асинхронный провайдер без параметров, возвращающий [T] или [Future<T>].
|
|
||||||
/// ENG: Synchronous or asynchronous provider without parameters, returning [T] or [Future<T>].
|
|
||||||
typedef Provider<T> = FutureOr<T> Function();
|
|
||||||
|
|
||||||
/// RU: Провайдер с динамическим параметром, возвращающий [T] или [Future<T>] в зависимости от реализации.
|
|
||||||
/// ENG: Provider with dynamic parameter, returning [T] or [Future<T>] depending on implementation.
|
|
||||||
typedef ProviderWithParams<T> = FutureOr<T> Function(dynamic);
|
|
||||||
|
|
||||||
/// RU: Абстрактный интерфейс для классов, которые разрешают зависимости типа [T].
|
|
||||||
/// ENG: Abstract interface for classes that resolve dependencies of type [T].
|
|
||||||
abstract class BindingResolver<T> {
|
|
||||||
/// RU: Синхронное разрешение зависимости с параметром [params].
|
|
||||||
/// ENG: Synchronous resolution of the dependency with [params].
|
|
||||||
T? resolveSync([dynamic params]);
|
|
||||||
|
|
||||||
/// RU: Асинхронное разрешение зависимости с параметром [params].
|
|
||||||
/// ENG: Asynchronous resolution of the dependency with [params].
|
|
||||||
Future<T>? resolveAsync([dynamic params]);
|
|
||||||
|
|
||||||
/// RU: Помечает текущий резолвер как синглтон — результат будет закеширован.
|
|
||||||
/// ENG: Marks this resolver as singleton — result will be cached.
|
|
||||||
void toSingleton();
|
|
||||||
|
|
||||||
bool get isSingleton;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// RU: Резолвер, оборачивающий конкретный экземпляр [T] (или Future<T>), без вызова провайдера.
|
|
||||||
/// ENG: Resolver that wraps a concrete instance of [T] (or Future<T>), without provider invocation.
|
|
||||||
class InstanceResolver<T> implements BindingResolver<T> {
|
|
||||||
final Instance<T> _instance;
|
|
||||||
|
|
||||||
/// RU: Создаёт резолвер, оборачивающий значение [instance].
|
|
||||||
/// ENG: Creates a resolver that wraps the given [instance].
|
|
||||||
InstanceResolver(this._instance);
|
|
||||||
|
|
||||||
@override
|
|
||||||
T resolveSync([_]) {
|
|
||||||
if (_instance is T) return _instance;
|
|
||||||
throw StateError(
|
|
||||||
'Instance $_instance is Future; '
|
|
||||||
'use resolveAsync() instead',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<T> resolveAsync([_]) {
|
|
||||||
if (_instance is Future<T>) return _instance;
|
|
||||||
|
|
||||||
return Future.value(_instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void toSingleton() {}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool get isSingleton => true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// RU: Резолвер, оборачивающий провайдер, с возможностью синглтон-кеширования.
|
|
||||||
/// ENG: Resolver that wraps a provider, with optional singleton caching.
|
|
||||||
class ProviderResolver<T> implements BindingResolver<T> {
|
|
||||||
final ProviderWithParams<T> _provider;
|
|
||||||
final bool _withParams;
|
|
||||||
|
|
||||||
FutureOr<T>? _cache;
|
|
||||||
bool _singleton = false;
|
|
||||||
|
|
||||||
/// RU: Создаёт резолвер из произвольной функции [raw], поддерживающей ноль или один параметр.
|
|
||||||
/// ENG: Creates a resolver from arbitrary function [raw], supporting zero or one parameter.
|
|
||||||
ProviderResolver(
|
|
||||||
ProviderWithParams<T> provider, {
|
|
||||||
required bool withParams,
|
|
||||||
}) : _provider = provider,
|
|
||||||
_withParams = withParams;
|
|
||||||
|
|
||||||
@override
|
|
||||||
T resolveSync([dynamic params]) {
|
|
||||||
_checkParams(params);
|
|
||||||
|
|
||||||
final result = _cache ?? _provider(params);
|
|
||||||
|
|
||||||
if (result is T) {
|
|
||||||
if (_singleton) {
|
|
||||||
_cache ??= result;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw StateError(
|
|
||||||
'Provider [$_provider] return Future<$T>. Use resolveAsync() instead.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<T> resolveAsync([dynamic params]) {
|
|
||||||
_checkParams(params);
|
|
||||||
|
|
||||||
final result = _cache ?? _provider(params);
|
|
||||||
final target = result is Future<T> ? result : Future<T>.value(result);
|
|
||||||
|
|
||||||
if (_singleton) {
|
|
||||||
_cache ??= target;
|
|
||||||
}
|
|
||||||
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void toSingleton() {
|
|
||||||
_singleton = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool get isSingleton => _singleton;
|
|
||||||
|
|
||||||
/// RU: Проверяет, был ли передан параметр, если провайдер требует его.
|
|
||||||
/// ENG: Checks if parameter is passed when the provider expects it.
|
|
||||||
void _checkParams(dynamic params) {
|
|
||||||
if (_withParams && params == null) {
|
|
||||||
throw StateError(
|
|
||||||
'[$T] Params is null. Maybe you forgot to pass it?',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -13,9 +13,9 @@
|
|||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:cherrypick/src/binding.dart';
|
||||||
import 'package:cherrypick/src/cycle_detector.dart';
|
import 'package:cherrypick/src/cycle_detector.dart';
|
||||||
import 'package:cherrypick/src/global_cycle_detector.dart';
|
import 'package:cherrypick/src/global_cycle_detector.dart';
|
||||||
import 'package:cherrypick/src/binding_resolver.dart';
|
|
||||||
import 'package:cherrypick/src/module.dart';
|
import 'package:cherrypick/src/module.dart';
|
||||||
|
|
||||||
Scope openRootScope() => Scope(null);
|
Scope openRootScope() => Scope(null);
|
||||||
@@ -176,12 +176,33 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
/// RU: Внутренний метод для разрешения зависимостей без проверки циклических зависимостей.
|
/// RU: Внутренний метод для разрешения зависимостей без проверки циклических зависимостей.
|
||||||
/// ENG: Internal method for dependency resolution without circular dependency checking.
|
/// ENG: Internal method for dependency resolution without circular dependency checking.
|
||||||
T? _tryResolveInternal<T>({String? named, dynamic params}) {
|
T? _tryResolveInternal<T>({String? named, dynamic params}) {
|
||||||
final resolver = _findBindingResolver<T>(named);
|
|
||||||
|
|
||||||
// 1 Поиск зависимости по всем модулям текущего скоупа
|
// 1 Поиск зависимости по всем модулям текущего скоупа
|
||||||
return resolver?.resolveSync(params) ??
|
if (_modulesList.isNotEmpty) {
|
||||||
// 2 Поиск зависимостей в родительском скоупе
|
for (var module in _modulesList) {
|
||||||
_parentScope?.tryResolve(named: named, params: params);
|
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.providerInstance:
|
||||||
|
return binding.provider;
|
||||||
|
case Mode.providerInstanceWithParams:
|
||||||
|
if (params == null) {
|
||||||
|
throw StateError('Param is null. Maybe you forget pass it');
|
||||||
|
}
|
||||||
|
return binding.providerWithParams(params);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2 Поиск зависимостей в родительском скоупе
|
||||||
|
return _parentScope?._tryResolveInternal(named: named, params: params);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Асинхронно возвращает найденную зависимость, определенную параметром типа [T].
|
/// RU: Асинхронно возвращает найденную зависимость, определенную параметром типа [T].
|
||||||
@@ -245,25 +266,30 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
/// RU: Внутренний метод для асинхронного разрешения зависимостей без проверки циклических зависимостей.
|
/// RU: Внутренний метод для асинхронного разрешения зависимостей без проверки циклических зависимостей.
|
||||||
/// ENG: Internal method for async dependency resolution without circular dependency checking.
|
/// ENG: Internal method for async dependency resolution without circular dependency checking.
|
||||||
Future<T?> _tryResolveAsyncInternal<T>({String? named, dynamic params}) async {
|
Future<T?> _tryResolveAsyncInternal<T>({String? named, dynamic params}) async {
|
||||||
final resolver = _findBindingResolver<T>(named);
|
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))) {
|
||||||
|
if (binding.instanceAsync != null) {
|
||||||
|
return await binding.instanceAsync;
|
||||||
|
}
|
||||||
|
|
||||||
// 1 Поиск зависимости по всем модулям текущего скоупа
|
if (binding.asyncProvider != null) {
|
||||||
return resolver?.resolveAsync(params) ??
|
return await binding.asyncProvider?.call();
|
||||||
// 2 Поиск зависимостей в родительском скоупе
|
}
|
||||||
_parentScope?.tryResolveAsync(named: named, params: params);
|
|
||||||
}
|
|
||||||
|
|
||||||
BindingResolver<T>? _findBindingResolver<T>(String? named) {
|
if (binding.asyncProviderWithParams != null) {
|
||||||
for (var module in _modulesList) {
|
if (params == null) {
|
||||||
for (var binding in module.bindingSet) {
|
throw StateError('Param is null. Maybe you forget pass it');
|
||||||
if (binding.key == T &&
|
}
|
||||||
((!binding.isNamed && named == null) ||
|
return await binding.asyncProviderWithParams!(params);
|
||||||
(binding.isNamed && named == binding.name))) {
|
}
|
||||||
return binding.resolver as BindingResolver<T>?;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return _parentScope?._tryResolveAsyncInternal(named: named, params: params);
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
name: cherrypick
|
name: cherrypick
|
||||||
description: Cherrypick is a small dependency injection (DI) library for dart/flutter projects.
|
description: Cherrypick is a small dependency injection (DI) library for dart/flutter projects.
|
||||||
version: 3.0.0-dev.2
|
version: 3.0.0-dev.0
|
||||||
homepage: https://pese-git.github.io/cherrypick-site/
|
homepage: https://pese-git.github.io/cherrypick-site/
|
||||||
documentation: https://github.com/pese-git/cherrypick/wiki
|
documentation: https://github.com/pese-git/cherrypick/wiki
|
||||||
repository: https://github.com/pese-git/cherrypick
|
repository: https://github.com/pese-git/cherrypick
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import 'package:cherrypick/cherrypick.dart';
|
import 'package:cherrypick/src/binding.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
@@ -7,12 +7,12 @@ void main() {
|
|||||||
group('Without name', () {
|
group('Without name', () {
|
||||||
test('Returns null by default', () {
|
test('Returns null by default', () {
|
||||||
final binding = Binding<int>();
|
final binding = Binding<int>();
|
||||||
expect(binding.resolver, null);
|
expect(binding.instance, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Sets mode to instance', () {
|
test('Sets mode to instance', () {
|
||||||
final binding = Binding<int>().toInstance(5);
|
final binding = Binding<int>().toInstance(5);
|
||||||
expect(binding.resolver, isA<InstanceResolver<int>>());
|
expect(binding.mode, Mode.instance);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('isSingleton is true', () {
|
test('isSingleton is true', () {
|
||||||
@@ -22,19 +22,19 @@ void main() {
|
|||||||
|
|
||||||
test('Stores value', () {
|
test('Stores value', () {
|
||||||
final binding = Binding<int>().toInstance(5);
|
final binding = Binding<int>().toInstance(5);
|
||||||
expect(binding.resolver?.resolveSync(), 5);
|
expect(binding.instance, 5);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
group('With name', () {
|
group('With name', () {
|
||||||
test('Returns null by default', () {
|
test('Returns null by default', () {
|
||||||
final binding = Binding<int>().withName('n');
|
final binding = Binding<int>().withName('n');
|
||||||
expect(binding.resolver, null);
|
expect(binding.instance, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Sets mode to instance', () {
|
test('Sets mode to instance', () {
|
||||||
final binding = Binding<int>().withName('n').toInstance(5);
|
final binding = Binding<int>().withName('n').toInstance(5);
|
||||||
expect(binding.resolver, isA<InstanceResolver<int>>());
|
expect(binding.mode, Mode.instance);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Sets key', () {
|
test('Sets key', () {
|
||||||
@@ -49,7 +49,7 @@ void main() {
|
|||||||
|
|
||||||
test('Stores value', () {
|
test('Stores value', () {
|
||||||
final binding = Binding<int>().withName('n').toInstance(5);
|
final binding = Binding<int>().withName('n').toInstance(5);
|
||||||
expect(binding.resolver?.resolveSync(), 5);
|
expect(binding.instance, 5);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Sets name', () {
|
test('Sets name', () {
|
||||||
@@ -60,39 +60,45 @@ void main() {
|
|||||||
|
|
||||||
test('Multiple toInstance calls change value', () {
|
test('Multiple toInstance calls change value', () {
|
||||||
final binding = Binding<int>().toInstance(1).toInstance(2);
|
final binding = Binding<int>().toInstance(1).toInstance(2);
|
||||||
expect(binding.resolver?.resolveSync(), 2);
|
expect(binding.instance, 2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- Instance binding (asynchronous) ---
|
// --- Instance binding (asynchronous) ---
|
||||||
group('Async Instance Binding (toInstanceAsync)', () {
|
group('Async Instance Binding (toInstanceAsync)', () {
|
||||||
test('Resolves instanceAsync with expected value', () async {
|
test('Resolves instanceAsync with expected value', () async {
|
||||||
final binding = Binding<int>().toInstance(Future.value(42));
|
final binding = Binding<int>().toInstanceAsync(Future.value(42));
|
||||||
expect(await binding.resolveAsync(), 42);
|
expect(await binding.instanceAsync, 42);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Does not affect instance', () {
|
||||||
|
final binding = Binding<int>().toInstanceAsync(Future.value(5));
|
||||||
|
expect(binding.instance, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Sets mode to instance', () {
|
test('Sets mode to instance', () {
|
||||||
final binding = Binding<int>().toInstance(Future.value(5));
|
final binding = Binding<int>().toInstanceAsync(Future.value(5));
|
||||||
expect(binding.resolver, isA<InstanceResolver<int>>());
|
expect(binding.mode, Mode.instance);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('isSingleton is true after toInstanceAsync', () {
|
test('isSingleton is true after toInstanceAsync', () {
|
||||||
final binding = Binding<int>().toInstance(Future.value(5));
|
final binding = Binding<int>().toInstanceAsync(Future.value(5));
|
||||||
expect(binding.isSingleton, isTrue);
|
expect(binding.isSingleton, isTrue);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Composes with withName', () async {
|
test('Composes with withName', () async {
|
||||||
final binding =
|
final binding = Binding<int>()
|
||||||
Binding<int>().withName('asyncValue').toInstance(Future.value(7));
|
.withName('asyncValue')
|
||||||
|
.toInstanceAsync(Future.value(7));
|
||||||
expect(binding.isNamed, isTrue);
|
expect(binding.isNamed, isTrue);
|
||||||
expect(binding.name, 'asyncValue');
|
expect(binding.name, 'asyncValue');
|
||||||
expect(await binding.resolveAsync(), 7);
|
expect(await binding.instanceAsync, 7);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Keeps value after multiple awaits', () async {
|
test('Keeps value after multiple awaits', () async {
|
||||||
final binding = Binding<int>().toInstance(Future.value(123));
|
final binding = Binding<int>().toInstanceAsync(Future.value(123));
|
||||||
final result1 = await binding.resolveAsync();
|
final result1 = await binding.instanceAsync;
|
||||||
final result2 = await binding.resolveAsync();
|
final result2 = await binding.instanceAsync;
|
||||||
expect(result1, equals(result2));
|
expect(result1, equals(result2));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -102,12 +108,12 @@ void main() {
|
|||||||
group('Without name', () {
|
group('Without name', () {
|
||||||
test('Returns null by default', () {
|
test('Returns null by default', () {
|
||||||
final binding = Binding<int>();
|
final binding = Binding<int>();
|
||||||
expect(binding.resolver, null);
|
expect(binding.provider, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Sets mode to providerInstance', () {
|
test('Sets mode to providerInstance', () {
|
||||||
final binding = Binding<int>().toProvide(() => 5);
|
final binding = Binding<int>().toProvide(() => 5);
|
||||||
expect(binding.resolver, isA<ProviderResolver<int>>());
|
expect(binding.mode, Mode.providerInstance);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('isSingleton is false by default', () {
|
test('isSingleton is false by default', () {
|
||||||
@@ -117,19 +123,19 @@ void main() {
|
|||||||
|
|
||||||
test('Returns provided value', () {
|
test('Returns provided value', () {
|
||||||
final binding = Binding<int>().toProvide(() => 5);
|
final binding = Binding<int>().toProvide(() => 5);
|
||||||
expect(binding.resolveSync(), 5);
|
expect(binding.provider, 5);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
group('With name', () {
|
group('With name', () {
|
||||||
test('Returns null by default', () {
|
test('Returns null by default', () {
|
||||||
final binding = Binding<int>().withName('n');
|
final binding = Binding<int>().withName('n');
|
||||||
expect(binding.resolver, null);
|
expect(binding.provider, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Sets mode to providerInstance', () {
|
test('Sets mode to providerInstance', () {
|
||||||
final binding = Binding<int>().withName('n').toProvide(() => 5);
|
final binding = Binding<int>().withName('n').toProvide(() => 5);
|
||||||
expect(binding.resolver, isA<ProviderResolver<int>>());
|
expect(binding.mode, Mode.providerInstance);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Sets key', () {
|
test('Sets key', () {
|
||||||
@@ -144,7 +150,7 @@ void main() {
|
|||||||
|
|
||||||
test('Returns provided value', () {
|
test('Returns provided value', () {
|
||||||
final binding = Binding<int>().withName('n').toProvide(() => 5);
|
final binding = Binding<int>().withName('n').toProvide(() => 5);
|
||||||
expect(binding.resolveSync(), 5);
|
expect(binding.provider, 5);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Sets name', () {
|
test('Sets name', () {
|
||||||
@@ -157,14 +163,14 @@ void main() {
|
|||||||
// --- Async provider binding ---
|
// --- Async provider binding ---
|
||||||
group('Async Provider Binding', () {
|
group('Async Provider Binding', () {
|
||||||
test('Resolves asyncProvider value', () async {
|
test('Resolves asyncProvider value', () async {
|
||||||
final binding = Binding<int>().toProvide(() async => 5);
|
final binding = Binding<int>().toProvideAsync(() async => 5);
|
||||||
expect(await binding.resolveAsync(), 5);
|
expect(await binding.asyncProvider?.call(), 5);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Resolves asyncProviderWithParams value', () async {
|
test('Resolves asyncProviderWithParams value', () async {
|
||||||
final binding = Binding<int>()
|
final binding = Binding<int>()
|
||||||
.toProvideWithParams((param) async => 5 + (param as int));
|
.toProvideAsyncWithParams((param) async => 5 + (param as int));
|
||||||
expect(await binding.resolveAsync(3), 8);
|
expect(await binding.asyncProviderWithParams?.call(3), 8);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -173,7 +179,12 @@ void main() {
|
|||||||
group('Without name', () {
|
group('Without name', () {
|
||||||
test('Returns null if no provider set', () {
|
test('Returns null if no provider set', () {
|
||||||
final binding = Binding<int>().singleton();
|
final binding = Binding<int>().singleton();
|
||||||
expect(binding.resolver, null);
|
expect(binding.provider, null);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Sets mode to providerInstance', () {
|
||||||
|
final binding = Binding<int>().toProvide(() => 5).singleton();
|
||||||
|
expect(binding.mode, Mode.providerInstance);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('isSingleton is true', () {
|
test('isSingleton is true', () {
|
||||||
@@ -183,7 +194,7 @@ void main() {
|
|||||||
|
|
||||||
test('Returns singleton value', () {
|
test('Returns singleton value', () {
|
||||||
final binding = Binding<int>().toProvide(() => 5).singleton();
|
final binding = Binding<int>().toProvide(() => 5).singleton();
|
||||||
expect(binding.resolveSync(), 5);
|
expect(binding.provider, 5);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Returns same value each call and provider only called once', () {
|
test('Returns same value each call and provider only called once', () {
|
||||||
@@ -193,8 +204,8 @@ void main() {
|
|||||||
return counter;
|
return counter;
|
||||||
}).singleton();
|
}).singleton();
|
||||||
|
|
||||||
final first = binding.resolveSync();
|
final first = binding.provider;
|
||||||
final second = binding.resolveSync();
|
final second = binding.provider;
|
||||||
expect(first, equals(second));
|
expect(first, equals(second));
|
||||||
expect(counter, 1);
|
expect(counter, 1);
|
||||||
});
|
});
|
||||||
@@ -203,7 +214,13 @@ void main() {
|
|||||||
group('With name', () {
|
group('With name', () {
|
||||||
test('Returns null if no provider set', () {
|
test('Returns null if no provider set', () {
|
||||||
final binding = Binding<int>().withName('n').singleton();
|
final binding = Binding<int>().withName('n').singleton();
|
||||||
expect(binding.resolver, null);
|
expect(binding.provider, null);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Sets mode to providerInstance', () {
|
||||||
|
final binding =
|
||||||
|
Binding<int>().withName('n').toProvide(() => 5).singleton();
|
||||||
|
expect(binding.mode, Mode.providerInstance);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Sets key', () {
|
test('Sets key', () {
|
||||||
@@ -221,7 +238,7 @@ void main() {
|
|||||||
test('Returns singleton value', () {
|
test('Returns singleton value', () {
|
||||||
final binding =
|
final binding =
|
||||||
Binding<int>().withName('n').toProvide(() => 5).singleton();
|
Binding<int>().withName('n').toProvide(() => 5).singleton();
|
||||||
expect(binding.resolveSync(), 5);
|
expect(binding.provider, 5);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Sets name', () {
|
test('Sets name', () {
|
||||||
@@ -230,6 +247,12 @@ void main() {
|
|||||||
expect(binding.name, 'n');
|
expect(binding.name, 'n');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Chained withName and singleton preserves mode', () {
|
||||||
|
final binding =
|
||||||
|
Binding<int>().toProvide(() => 3).withName("named").singleton();
|
||||||
|
expect(binding.mode, Mode.providerInstance);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- WithName / Named binding, isNamed, edge-cases ---
|
// --- WithName / Named binding, isNamed, edge-cases ---
|
||||||
@@ -242,7 +265,7 @@ void main() {
|
|||||||
|
|
||||||
test('providerWithParams returns null if not set', () {
|
test('providerWithParams returns null if not set', () {
|
||||||
final binding = Binding<int>();
|
final binding = Binding<int>();
|
||||||
expect(binding.resolveSync(123), null);
|
expect(binding.providerWithParams(123), null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -200,13 +200,11 @@ class AsyncServiceB {
|
|||||||
class AsyncCircularModule extends Module {
|
class AsyncCircularModule extends Module {
|
||||||
@override
|
@override
|
||||||
void builder(Scope currentScope) {
|
void builder(Scope currentScope) {
|
||||||
// ignore: deprecated_member_use_from_same_package
|
|
||||||
bind<AsyncServiceA>().toProvideAsync(() async {
|
bind<AsyncServiceA>().toProvideAsync(() async {
|
||||||
final serviceB = await currentScope.resolveAsync<AsyncServiceB>();
|
final serviceB = await currentScope.resolveAsync<AsyncServiceB>();
|
||||||
return AsyncServiceA(serviceB);
|
return AsyncServiceA(serviceB);
|
||||||
});
|
});
|
||||||
|
|
||||||
// ignore: deprecated_member_use_from_same_package
|
|
||||||
bind<AsyncServiceB>().toProvideAsync(() async {
|
bind<AsyncServiceB>().toProvideAsync(() async {
|
||||||
final serviceA = await currentScope.resolveAsync<AsyncServiceA>();
|
final serviceA = await currentScope.resolveAsync<AsyncServiceA>();
|
||||||
return AsyncServiceB(serviceA);
|
return AsyncServiceB(serviceA);
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ void main() {
|
|||||||
final scope = Scope(null)
|
final scope = Scope(null)
|
||||||
..installModules([
|
..installModules([
|
||||||
_InlineModule((m, s) {
|
_InlineModule((m, s) {
|
||||||
m.bind<String>().toInstance(Future.value('async value'));
|
m.bind<String>().toInstanceAsync(Future.value('async value'));
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
expect(await scope.resolveAsync<String>(), "async value");
|
expect(await scope.resolveAsync<String>(), "async value");
|
||||||
@@ -137,7 +137,7 @@ void main() {
|
|||||||
final scope = Scope(null)
|
final scope = Scope(null)
|
||||||
..installModules([
|
..installModules([
|
||||||
_InlineModule((m, s) {
|
_InlineModule((m, s) {
|
||||||
m.bind<int>().toProvide(() async => 7);
|
m.bind<int>().toProvideAsync(() async => 7);
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
expect(await scope.resolveAsync<int>(), 7);
|
expect(await scope.resolveAsync<int>(), 7);
|
||||||
@@ -147,7 +147,7 @@ void main() {
|
|||||||
final scope = Scope(null)
|
final scope = Scope(null)
|
||||||
..installModules([
|
..installModules([
|
||||||
_InlineModule((m, s) {
|
_InlineModule((m, s) {
|
||||||
m.bind<int>().toProvideWithParams((x) async => (x as int) * 3);
|
m.bind<int>().toProvideAsyncWithParams((x) async => (x as int) * 3);
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
expect(await scope.resolveAsync<int>(params: 2), 6);
|
expect(await scope.resolveAsync<int>(params: 2), 6);
|
||||||
|
|||||||
@@ -1,11 +1,3 @@
|
|||||||
## 1.1.3-dev.2
|
|
||||||
|
|
||||||
- Update a dependency to the latest release.
|
|
||||||
|
|
||||||
## 1.1.3-dev.1
|
|
||||||
|
|
||||||
- Update a dependency to the latest release.
|
|
||||||
|
|
||||||
## 1.1.3-dev.0
|
## 1.1.3-dev.0
|
||||||
|
|
||||||
- **FIX**: update deps.
|
- **FIX**: update deps.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
name: cherrypick_flutter
|
name: cherrypick_flutter
|
||||||
description: "Flutter library that allows access to the root scope through the context using `CherryPickProvider`."
|
description: "Flutter library that allows access to the root scope through the context using `CherryPickProvider`."
|
||||||
version: 1.1.3-dev.2
|
version: 1.1.3-dev.0
|
||||||
homepage: https://pese-git.github.io/cherrypick-site/
|
homepage: https://pese-git.github.io/cherrypick-site/
|
||||||
documentation: https://github.com/pese-git/cherrypick/wiki
|
documentation: https://github.com/pese-git/cherrypick/wiki
|
||||||
repository: https://github.com/pese-git/cherrypick
|
repository: https://github.com/pese-git/cherrypick
|
||||||
@@ -13,7 +13,7 @@ environment:
|
|||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
cherrypick: ^3.0.0-dev.2
|
cherrypick: ^3.0.0-dev.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ packages:
|
|||||||
path: "../../cherrypick"
|
path: "../../cherrypick"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "3.0.0-dev.1"
|
version: "2.2.0"
|
||||||
cherrypick_annotations:
|
cherrypick_annotations:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -141,7 +141,7 @@ packages:
|
|||||||
path: "../../cherrypick_flutter"
|
path: "../../cherrypick_flutter"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "1.1.3-dev.1"
|
version: "1.1.2"
|
||||||
cherrypick_generator:
|
cherrypick_generator:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ packages:
|
|||||||
path: "../../cherrypick"
|
path: "../../cherrypick"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "3.0.0-dev.1"
|
version: "2.2.0"
|
||||||
cherrypick_annotations:
|
cherrypick_annotations:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ name: cherrypick_workspace
|
|||||||
sdkPath: .fvm/flutter_sdk
|
sdkPath: .fvm/flutter_sdk
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
- benchmark_cherrypick
|
|
||||||
- cherrypick
|
- cherrypick
|
||||||
- cherrypick_flutter
|
- cherrypick_flutter
|
||||||
- cherrypick_annotations
|
- cherrypick_annotations
|
||||||
|
|||||||
Reference in New Issue
Block a user