mirror of
https://github.com/pese-git/cherrypick.git
synced 2026-01-24 21:57:58 +00:00
Compare commits
46 Commits
cherrypick
...
cherrypick
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
900cd68663 | ||
|
|
57e4196b95 | ||
|
|
358da8f96b | ||
|
|
ea2b6687f4 | ||
|
|
df00a2a5d2 | ||
|
|
d5983a4a0b | ||
|
|
125bccfa5a | ||
|
|
12b97c9368 | ||
|
|
424aaa3e22 | ||
|
|
2ec3a86a2f | ||
|
|
efed72cc39 | ||
|
|
4dc9e269cd | ||
|
|
d153ab4255 | ||
|
|
6924ccd07b | ||
|
|
26b843f791 | ||
|
|
8eafba4e4b | ||
|
|
ad6e9bbc3d | ||
|
|
bea8affcab | ||
|
|
1d7b9a9166 | ||
|
|
016c212063 | ||
|
|
d93d4173a2 | ||
|
|
85aa23d7ed | ||
|
|
51cf4a0dc0 | ||
|
|
8f980ff111 | ||
|
|
4d872d7c25 | ||
|
|
450f4231cb | ||
|
|
cd1b9cf49d | ||
|
|
33775f5748 | ||
|
|
e5848784ac | ||
|
|
40b3cbb422 | ||
|
|
a4b0ddfa54 | ||
|
|
547a15fa4e | ||
|
|
a9c95f6a89 | ||
|
|
61f2268d63 | ||
|
|
f6fcb76730 | ||
|
|
f8bbaf6c2c | ||
|
|
2ebc997fea | ||
|
|
d15f3063fc | ||
|
|
1e8b8db64a | ||
|
|
c3ec52823e | ||
|
|
16e05d27c5 | ||
|
|
1131be44da | ||
|
|
c971b59483 | ||
|
|
aa97632add | ||
|
|
41d49e98d0 | ||
|
|
44a8a3fcb2 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -18,5 +18,7 @@ pubspec_overrides.yaml
|
|||||||
melos_cherrypick.iml
|
melos_cherrypick.iml
|
||||||
melos_cherrypick_workspace.iml
|
melos_cherrypick_workspace.iml
|
||||||
melos_cherrypick_flutter.iml
|
melos_cherrypick_flutter.iml
|
||||||
|
melos_benchmark_di.iml
|
||||||
|
melos_talker_cherrypick_logger.iml
|
||||||
|
|
||||||
coverage
|
coverage
|
||||||
110
CHANGELOG.md
110
CHANGELOG.md
@@ -3,6 +3,116 @@
|
|||||||
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-12
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Packages with breaking changes:
|
||||||
|
|
||||||
|
- There are no breaking changes in this release.
|
||||||
|
|
||||||
|
Packages with other changes:
|
||||||
|
|
||||||
|
- [`cherrypick` - `v3.0.0-dev.8`](#cherrypick---v300-dev8)
|
||||||
|
- [`cherrypick_flutter` - `v1.1.3-dev.8`](#cherrypick_flutter---v113-dev8)
|
||||||
|
|
||||||
|
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.8`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `cherrypick` - `v3.0.0-dev.8`
|
||||||
|
|
||||||
|
- **REFACTOR**(tests): replace MockLogger with MockObserver in scope tests to align with updated observer API.
|
||||||
|
- **FIX**(doc): remove hide symbol.
|
||||||
|
- **FEAT**(core): add full DI lifecycle observability via onInstanceDisposed.
|
||||||
|
- **DOCS**(logging): update Logging section in README with modern Observer usage and Talker integration examples.
|
||||||
|
- **DOCS**(observer): improve documentation, translate all comments to English, add usage examples.
|
||||||
|
- **DOCS**(README): add section with overview table for additional modules.
|
||||||
|
- **DOCS**(README): refactor structure and improve clarity of advanced features.
|
||||||
|
- **DOCS**(README): add 'Hierarchical Subscopes' section and update structure for advanced features clarity.
|
||||||
|
|
||||||
|
|
||||||
|
## 2025-08-11
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Packages with breaking changes:
|
||||||
|
|
||||||
|
- [`cherrypick` - `v3.0.0-dev.7`](#cherrypick---v300-dev7)
|
||||||
|
|
||||||
|
Packages with other changes:
|
||||||
|
|
||||||
|
- [`cherrypick_annotations` - `v1.1.1`](#cherrypick_annotations---v111)
|
||||||
|
- [`cherrypick_flutter` - `v1.1.3-dev.7`](#cherrypick_flutter---v113-dev7)
|
||||||
|
- [`cherrypick_generator` - `v1.1.1`](#cherrypick_generator---v111)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `cherrypick` - `v3.0.0-dev.7`
|
||||||
|
|
||||||
|
- **FIX**(comment): fix warnings.
|
||||||
|
- **FIX**(license): correct urls.
|
||||||
|
- **FEAT**: add Disposable interface source and usage example.
|
||||||
|
- **DOCS**(readme): add comprehensive section on annotations and DI code generation.
|
||||||
|
- **DOCS**(readme): add detailed section and examples for automatic Disposable resource cleanup\n\n- Added a dedicated section with English description and code samples on using Disposable for automatic resource management.\n- Updated Features to include automatic resource cleanup for Disposable dependencies.\n\nHelps developers understand and implement robust DI resource management practices.
|
||||||
|
- **DOCS**(faq): add best practice FAQ about using await with scope disposal.
|
||||||
|
- **DOCS**(faq): add best practice FAQ about using await with scope disposal.
|
||||||
|
- **BREAKING** **REFACTOR**(core): make closeRootScope async and await dispose.
|
||||||
|
- **BREAKING** **DOCS**(disposable): add detailed English documentation and usage examples for Disposable interface; chore: update binding_resolver and add explanatory comment in scope_test for deprecated usage.\n\n- Expanded Disposable interface docs, added sync & async example classes, and CherryPick integration sample.\n- Clarified how to implement and use Disposable in DI context.\n- Updated binding_resolver for internal improvements.\n- Added ignore for deprecated member use in scope_test for clarity and future upgrades.\n\nBREAKING CHANGE: Documentation style enhancement and clearer API usage for Disposable implementations.
|
||||||
|
|
||||||
|
#### `cherrypick_annotations` - `v1.1.1`
|
||||||
|
|
||||||
|
- **FIX**(license): correct urls.
|
||||||
|
|
||||||
|
#### `cherrypick_flutter` - `v1.1.3-dev.7`
|
||||||
|
|
||||||
|
- **FIX**(license): correct urls.
|
||||||
|
|
||||||
|
#### `cherrypick_generator` - `v1.1.1`
|
||||||
|
|
||||||
|
- **FIX**(license): correct urls.
|
||||||
|
|
||||||
|
|
||||||
|
## 2025-08-08
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Packages with breaking changes:
|
||||||
|
|
||||||
|
- [`cherrypick` - `v3.0.0-dev.6`](#cherrypick---v300-dev6)
|
||||||
|
|
||||||
|
Packages with other changes:
|
||||||
|
|
||||||
|
- [`cherrypick_flutter` - `v1.1.3-dev.6`](#cherrypick_flutter---v113-dev6)
|
||||||
|
|
||||||
|
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.6`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `cherrypick` - `v3.0.0-dev.6`
|
||||||
|
|
||||||
|
- **FIX**: improve global cycle detector logic.
|
||||||
|
- **DOCS**(readme): add comprehensive DI state and action logging to features.
|
||||||
|
- **DOCS**(helper): add complete DartDoc with real usage examples for CherryPick class.
|
||||||
|
- **DOCS**(log_format): add detailed English documentation for formatLogMessage function.
|
||||||
|
- **BREAKING** **FEAT**(core): refactor root scope API, improve logger injection, helpers, and tests.
|
||||||
|
- **BREAKING** **FEAT**(logger): add extensible logging API, usage examples, and bilingual documentation.
|
||||||
|
|
||||||
|
|
||||||
## 2025-08-07
|
## 2025-08-07
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -192,7 +192,7 @@
|
|||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
|||||||
@@ -1,79 +1,51 @@
|
|||||||
# DI Benchmark Results: cherrypick vs get_it
|
# Comparative DI Benchmark Report: cherrypick vs get_it vs riverpod
|
||||||
|
|
||||||
## Benchmark parameters
|
## Benchmark Scenarios
|
||||||
|
|
||||||
| Parameter | Value |
|
1. **RegisterSingleton** — Registers and resolves a singleton. Baseline DI speed.
|
||||||
|------------------|-----------------------|
|
2. **ChainSingleton** — A dependency chain A → B → ... → N (singleton). Deep singleton chain resolution.
|
||||||
| --benchmark | all |
|
3. **ChainFactory** — All chain elements are factories. Stateless creation chain.
|
||||||
| --chainCount (-c)| 10, 100 |
|
4. **AsyncChain** — Async chain (async factory). Performance on async graphs.
|
||||||
| --nestingDepth (-d)| 10, 100 |
|
5. **Named** — Registers two bindings with names, resolves by name. Named lookup test.
|
||||||
| --repeat (-r) | 2 |
|
6. **Override** — Registers a chain/alias in a child scope. Tests scope overrides.
|
||||||
| --warmup (-w) | 1 (default) |
|
|
||||||
| --format (-f) | markdown |
|
|
||||||
| --di | cherrypick, get_it |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Benchmark scenarios
|
## Comparative Table: chainCount=10, nestingDepth=10 (Mean, PeakRSS)
|
||||||
|
|
||||||
**(1) RegisterSingleton**
|
| Scenario | cherrypick Mean (us) | cherrypick PeakRSS | get_it Mean (us) | get_it PeakRSS | riverpod Mean (us) | riverpod PeakRSS |
|
||||||
Registers and resolves a singleton. Baseline DI speed.
|
|--------------------|---------------------:|-------------------:|-----------------:|---------------:|-------------------:|-----------------:|
|
||||||
|
| RegisterSingleton | 13.00 | 273104 | 8.40 | 261872 | 9.80 | 268512 |
|
||||||
|
| ChainSingleton | 13.80 | 271072 | 2.00 | 262000 | 33.60 | 268784 |
|
||||||
|
| ChainFactory | 5.00 | 299216 | 4.00 | 297136 | 22.80 | 271296 |
|
||||||
|
| AsyncChain | 28.60 | 290640 | 24.60 | 342976 | 78.20 | 285920 |
|
||||||
|
| Named | 2.20 | 297008 | 0.20 | 449824 | 6.20 | 281136 |
|
||||||
|
| Override | 7.00 | 297024 | 0.00 | 449824 | 30.20 | 281152 |
|
||||||
|
|
||||||
**(2) ChainSingleton**
|
## Maximum Load: chainCount=100, nestingDepth=100 (Mean, PeakRSS)
|
||||||
A dependency chain A → B → ... → N (singleton). Measures how fast DI resolves deep singleton chains by name.
|
|
||||||
|
|
||||||
**(3) ChainFactory**
|
| Scenario | cherrypick Mean (us) | cherrypick PeakRSS | get_it Mean (us) | get_it PeakRSS | riverpod Mean (us) | riverpod PeakRSS |
|
||||||
Same as ChainSingleton, but every chain element is a factory. Shows DI speed for stateless 'creation chain'.
|
|--------------------|---------------------:|-------------------:|-----------------:|---------------:|-------------------:|-----------------:|
|
||||||
|
| RegisterSingleton | 4.00 | 271072 | 1.00 | 262000 | 2.00 | 268688 |
|
||||||
**(4) AsyncChain**
|
| ChainSingleton | 76.60 | 303312 | 2.00 | 297136 | 221.80 | 270784 |
|
||||||
Async chain (async factory). Measures DI performance for async graphs.
|
| ChainFactory | 80.00 | 293952 | 39.20 | 342720 | 195.80 | 308640 |
|
||||||
|
| AsyncChain | 251.40 | 297008 | 18.20 | 450640 | 748.80 | 285968 |
|
||||||
**(5) Named**
|
| Named | 2.20 | 297008 | 0.00 | 449824 | 1.00 | 281136 |
|
||||||
Registers two bindings with names ("impl1", "impl2"), resolves by name. Tests named lookup.
|
| Override | 104.80 | 301632 | 2.20 | 477344 | 120.80 | 294752 |
|
||||||
|
|
||||||
**(6) Override**
|
|
||||||
Registers a chain/alias in a child scope and resolves UniversalService without a name in that scope. Simulates override and modular/test architecture.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Comparative Table (Mean, ΔRSS), chainCount=10, nestingDepth=10
|
## Analysis
|
||||||
|
|
||||||
| Scenario | cherrypick Mean (us) | cherrypick ΔRSS | get_it Mean (us) | get_it ΔRSS |
|
- **get_it** is the absolute leader in all scenarios, especially under deep/nested chains and async.
|
||||||
|--------------------|---------------------:|----------------:|-----------------:|------------:|
|
- **cherrypick** is highly competitive and much faster than riverpod on any complex graph.
|
||||||
| RegisterSingleton | 21.0 | 320 | 24.5 | 80 |
|
- **riverpod** is only suitable for small/simple DI graphs due to major slowdowns with depth, async, or override.
|
||||||
| ChainSingleton | 112.5 | -3008 | 2.0 | 304 |
|
|
||||||
| ChainFactory | 8.0 | 0 | 4.0 | 0 |
|
|
||||||
| AsyncChain | 36.5 | 0 | 13.5 | 0 |
|
|
||||||
| Named | 1.5 | 0 | 0.5 | 0 |
|
|
||||||
| Override | 27.5 | 0 | 0.0 | 0 |
|
|
||||||
|
|
||||||
## Maximum load: chainCount=100, nestingDepth=100
|
### Recommendations
|
||||||
|
- Use **get_it** for performance-critical and deeply nested graphs.
|
||||||
| Scenario | cherrypick Mean (us) | cherrypick ΔRSS | get_it Mean (us) | get_it ΔRSS |
|
- Use **cherrypick** for scalable/testable apps if a small speed loss is acceptable.
|
||||||
|--------------------|---------------------:|----------------:|-----------------:|------------:|
|
- Use **riverpod** only if you rely on Flutter integration and your DI chains are simple.
|
||||||
| RegisterSingleton | 1.0 | 32 | 1.0 | 0 |
|
|
||||||
| ChainSingleton | 3884.0 | 0 | 1.5 | 34848 |
|
|
||||||
| ChainFactory | 4088.0 | 0 | 50.0 | 12528 |
|
|
||||||
| AsyncChain | 4287.0 | 0 | 17.0 | 63120 |
|
|
||||||
| Named | 1.0 | 0 | 0.0 | 0 |
|
|
||||||
| Override | 4767.5 | 0 | 1.5 | 14976 |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Scenario explanations
|
_Last updated: August 8, 2025._
|
||||||
|
|
||||||
- **RegisterSingleton:** Registers and resolves a singleton dependency, baseline test for cold/hot startup speed.
|
|
||||||
- **ChainSingleton:** Deep chain of singleton dependencies. Cherrypick is much slower as depth increases; get_it is nearly unaffected.
|
|
||||||
- **ChainFactory:** Creation chain with new instances per resolve. get_it generally faster on large chains due to ultra-simple factory registration.
|
|
||||||
- **AsyncChain:** Async factory chain. get_it processes async resolutions much faster; cherrypick is much slower as depth increases due to async handling.
|
|
||||||
- **Named:** Both DI containers resolve named bindings nearly instantly, even on large graphs.
|
|
||||||
- **Override:** Child scope override. get_it (thanks to stack-based scopes) resolves immediately; cherrypick supports modular testing with controlled memory use.
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
- **get_it** demonstrates impressive speed and low overhead across all scenarios and loads, but lacks diagnostics, advanced scopes, and cycle detection.
|
|
||||||
- **cherrypick** is ideal for complex, multi-layered, production or testable architectures where scope, overrides, and diagnostics are critical. Predictably slower on deep/wide graphs, but scales well and provides extra safety.
|
|
||||||
|
|
||||||
**Recommendation:**
|
|
||||||
- Use cherrypick for enterprise, multi-feature/testable DI needs.
|
|
||||||
- Use get_it for fast games, scripts, tiny Apps, and hot demos.
|
|
||||||
|
|||||||
@@ -1,79 +1,51 @@
|
|||||||
# Результаты бенчмарка DI: cherrypick vs get_it
|
# Сравнительный отчет DI-бенчмарка: cherrypick vs get_it vs riverpod
|
||||||
|
|
||||||
## Параметры запуска бенчмарков
|
## Описание сценариев
|
||||||
|
|
||||||
| Параметр | Значение |
|
1. **RegisterSingleton** — регистрация и получение объекта-синглтона (базовая скорость DI).
|
||||||
|------------------|-------------------------|
|
2. **ChainSingleton** — цепочка зависимостей A → B → ... → N (singleton). Глубокий singleton-резолвинг.
|
||||||
| --benchmark | all |
|
3. **ChainFactory** — все элементы цепочки — фабрики. Stateless построение графа.
|
||||||
| --chainCount (-c)| 10, 100 |
|
4. **AsyncChain** — асинхронная цепочка (async factory). Тестирует async/await граф.
|
||||||
| --nestingDepth (-d)| 10, 100 |
|
5. **Named** — регистрация двух биндингов с именами, разрешение по имени.
|
||||||
| --repeat (-r) | 2 |
|
6. **Override** — регистрация биндинга/цепочки в дочернем scope. Проверка override/scoping.
|
||||||
| --warmup (-w) | 1 (по умолчанию) |
|
|
||||||
| --format (-f) | markdown |
|
|
||||||
| --di | cherrypick, get_it |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Описание бенчмарков
|
## Сводная таблица: chainCount=10, nestingDepth=10 (Mean, PeakRSS)
|
||||||
|
|
||||||
**(1) RegisterSingleton**
|
| Сценарий | cherrypick Mean (мкс) | cherrypick PeakRSS | get_it Mean (мкс) | get_it PeakRSS | riverpod Mean (мкс) | riverpod PeakRSS |
|
||||||
Регистрируется и дважды резолвится singleton. Базовый тест скорости DI.
|
|--------------------|----------------------:|-------------------:|------------------:|---------------:|--------------------:|-----------------:|
|
||||||
|
| RegisterSingleton | 13.00 | 273104 | 8.40 | 261872 | 9.80 | 268512 |
|
||||||
|
| ChainSingleton | 13.80 | 271072 | 2.00 | 262000 | 33.60 | 268784 |
|
||||||
|
| ChainFactory | 5.00 | 299216 | 4.00 | 297136 | 22.80 | 271296 |
|
||||||
|
| AsyncChain | 28.60 | 290640 | 24.60 | 342976 | 78.20 | 285920 |
|
||||||
|
| Named | 2.20 | 297008 | 0.20 | 449824 | 6.20 | 281136 |
|
||||||
|
| Override | 7.00 | 297024 | 0.00 | 449824 | 30.20 | 281152 |
|
||||||
|
|
||||||
**(2) ChainSingleton**
|
## Максимальная нагрузка: chainCount=100, nestingDepth=100 (Mean, PeakRSS)
|
||||||
Цепочка зависимостей A → B → ... → N (singleton). Тестирует скорость заполнения и разрешения глубоких singleton-цепочек по имени.
|
|
||||||
|
|
||||||
**(3) ChainFactory**
|
| Сценарий | cherrypick Mean (мкс) | cherrypick PeakRSS | get_it Mean (мкс) | get_it PeakRSS | riverpod Mean (мкс) | riverpod PeakRSS |
|
||||||
Аналогично ChainSingleton, но каждое звено цепи — factory (новый объект при каждом resolve).
|
|--------------------|----------------------:|-------------------:|------------------:|---------------:|--------------------:|-----------------:|
|
||||||
|
| RegisterSingleton | 4.00 | 271072 | 1.00 | 262000 | 2.00 | 268688 |
|
||||||
**(4) AsyncChain**
|
| ChainSingleton | 76.60 | 303312 | 2.00 | 297136 | 221.80 | 270784 |
|
||||||
Асинхронная цепочка (async factory). Важно для сценариев с async DI.
|
| ChainFactory | 80.00 | 293952 | 39.20 | 342720 | 195.80 | 308640 |
|
||||||
|
| AsyncChain | 251.40 | 297008 | 18.20 | 450640 | 748.80 | 285968 |
|
||||||
**(5) Named**
|
| Named | 2.20 | 297008 | 0.00 | 449824 | 1.00 | 281136 |
|
||||||
Регистрируются две реализации по имени ('impl1', 'impl2'), разрешается named. Проверка lookup по имени.
|
| Override | 104.80 | 301632 | 2.20 | 477344 | 120.80 | 294752 |
|
||||||
|
|
||||||
**(6) Override**
|
|
||||||
Регистрируется цепочка/alias в дочернем scope, резолвится UniversalService без имени там же. Симуляция override и изолированной/тестовой архитектуры.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Сравнительная таблица (Mean (us), ΔRSS(KB)), chainCount=10, nestingDepth=10
|
## Краткий анализ и рекомендации
|
||||||
|
|
||||||
| Сценарий | cherrypick Mean (мкс) | cherrypick ΔRSS | get_it Mean (мкс) | get_it ΔRSS |
|
- **get_it** всегда лидер, особенно на глубине/асинхронных графах.
|
||||||
|-------------------|---------------------:|----------------:|-----------------:|------------:|
|
- **cherrypick** заметно быстрее riverpod на сложных сценариях, опережая его в разы.
|
||||||
| RegisterSingleton | 21.0 | 320 | 24.5 | 80 |
|
- **riverpod** подходит только для простых/небольших графов — при росте глубины или async/override резко проигрывает по скорости.
|
||||||
| ChainSingleton | 112.5 | -3008 | 2.0 | 304 |
|
|
||||||
| ChainFactory | 8.0 | 0 | 4.0 | 0 |
|
|
||||||
| AsyncChain | 36.5 | 0 | 13.5 | 0 |
|
|
||||||
| Named | 1.5 | 0 | 0.5 | 0 |
|
|
||||||
| Override | 27.5 | 0 | 0.0 | 0 |
|
|
||||||
|
|
||||||
## Максимальная нагрузка: chainCount=100, nestingDepth=100
|
### Рекомендации
|
||||||
|
- Используйте **get_it** для критичных к скорости приложений/сложных графов зависимостей.
|
||||||
| Сценарий | cherrypick Mean (мкс) | cherrypick ΔRSS | get_it Mean (мкс) | get_it ΔRSS |
|
- Выбирайте **cherrypick** для масштабируемых, тестируемых архитектур, если микросекундная разница не критична.
|
||||||
|-------------------|---------------------:|----------------:|-----------------:|------------:|
|
- **riverpod** уместен только для реактивного UI или простых графов DI.
|
||||||
| RegisterSingleton | 1.0 | 32 | 1.0 | 0 |
|
|
||||||
| ChainSingleton | 3884.0 | 0 | 1.5 | 34848 |
|
|
||||||
| ChainFactory | 4088.0 | 0 | 50.0 | 12528 |
|
|
||||||
| AsyncChain | 4287.0 | 0 | 17.0 | 63120 |
|
|
||||||
| Named | 1.0 | 0 | 0.0 | 0 |
|
|
||||||
| Override | 4767.5 | 0 | 1.5 | 14976 |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Пояснения по сценариям
|
_Обновлено: 8 августа 2025_
|
||||||
|
|
||||||
- **RegisterSingleton** — базовый тест DI (регистрация и резолвинг singleton). Практически мгновенно у обоих DI.
|
|
||||||
- **ChainSingleton** — глубокая singleton-цепочка. get_it вне конкуренции по скорости, cherrypick медленнее из-за более сложной логики поиска именованных зависимостей, но предсказуем.
|
|
||||||
- **ChainFactory** — цепочка Factory-объектов. cherrypick заметно медленнее на длинных цепях, get_it почти не увеличивает время.
|
|
||||||
- **AsyncChain** — асинхронная цепочка сервисов. get_it существенно быстрее, cherrypick страдает на глубине/ширине.
|
|
||||||
- **Named** — разрешение зависимостей по имени. Оба DI почти мгновенны.
|
|
||||||
- **Override** — переопределение alias без имени в дочернем scope. get_it (со стековыми scope) почти не теряет времени; cherrypick предсказуемо замедляется на глубине/ширине.
|
|
||||||
|
|
||||||
## Итог
|
|
||||||
|
|
||||||
- **get_it** выдаёт отличную производительность по всем сценариям, особенно на больших графах; но не поддерживает продвинутую диагностику, проверки циклов, расширенные scope.
|
|
||||||
- **cherrypick** — незаменим для работы с корпоративными/тестируемыми архитектурами и наследованием, устойчиво ведёт себя на тысячи зависимостей, но требует учёта роста времени при экстремальных нагрузках.
|
|
||||||
|
|
||||||
**Рекомендация:**
|
|
||||||
- cherrypick — выбор для серьёзных production-систем и тестирования;
|
|
||||||
- get_it — лидер для MVP, быстрых демо, прототипов, CLI, games.
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ class RiverpodAdapter extends DIAdapter<Map<String, rp.ProviderBase<Object?>>> {
|
|||||||
rp.ProviderContainer? _container;
|
rp.ProviderContainer? _container;
|
||||||
final Map<String, rp.ProviderBase<Object?>> _namedProviders;
|
final Map<String, rp.ProviderBase<Object?>> _namedProviders;
|
||||||
final rp.ProviderContainer? _parent;
|
final rp.ProviderContainer? _parent;
|
||||||
final bool _isSubScope;
|
|
||||||
|
|
||||||
RiverpodAdapter({
|
RiverpodAdapter({
|
||||||
rp.ProviderContainer? container,
|
rp.ProviderContainer? container,
|
||||||
@@ -18,8 +17,7 @@ class RiverpodAdapter extends DIAdapter<Map<String, rp.ProviderBase<Object?>>> {
|
|||||||
bool isSubScope = false,
|
bool isSubScope = false,
|
||||||
}) : _container = container,
|
}) : _container = container,
|
||||||
_namedProviders = providers ?? <String, rp.ProviderBase<Object?>>{},
|
_namedProviders = providers ?? <String, rp.ProviderBase<Object?>>{},
|
||||||
_parent = parent,
|
_parent = parent;
|
||||||
_isSubScope = isSubScope;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void setupDependencies(void Function(Map<String, rp.ProviderBase<Object?>> container) registration) {
|
void setupDependencies(void Function(Map<String, rp.ProviderBase<Object?>> container) registration) {
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ packages:
|
|||||||
path: "../cherrypick"
|
path: "../cherrypick"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "3.0.0-dev.2"
|
version: "3.0.0-dev.7"
|
||||||
collection:
|
collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -1,3 +1,39 @@
|
|||||||
|
## 3.0.0-dev.8
|
||||||
|
|
||||||
|
- **REFACTOR**(tests): replace MockLogger with MockObserver in scope tests to align with updated observer API.
|
||||||
|
- **FIX**(doc): remove hide symbol.
|
||||||
|
- **FEAT**(core): add full DI lifecycle observability via onInstanceDisposed.
|
||||||
|
- **DOCS**(logging): update Logging section in README with modern Observer usage and Talker integration examples.
|
||||||
|
- **DOCS**(observer): improve documentation, translate all comments to English, add usage examples.
|
||||||
|
- **DOCS**(README): add section with overview table for additional modules.
|
||||||
|
- **DOCS**(README): refactor structure and improve clarity of advanced features.
|
||||||
|
- **DOCS**(README): add 'Hierarchical Subscopes' section and update structure for advanced features clarity.
|
||||||
|
|
||||||
|
## 3.0.0-dev.7
|
||||||
|
|
||||||
|
> Note: This release has breaking changes.
|
||||||
|
|
||||||
|
- **FIX**(comment): fix warnings.
|
||||||
|
- **FIX**(license): correct urls.
|
||||||
|
- **FEAT**: add Disposable interface source and usage example.
|
||||||
|
- **DOCS**(readme): add comprehensive section on annotations and DI code generation.
|
||||||
|
- **DOCS**(readme): add detailed section and examples for automatic Disposable resource cleanup\n\n- Added a dedicated section with English description and code samples on using Disposable for automatic resource management.\n- Updated Features to include automatic resource cleanup for Disposable dependencies.\n\nHelps developers understand and implement robust DI resource management practices.
|
||||||
|
- **DOCS**(faq): add best practice FAQ about using await with scope disposal.
|
||||||
|
- **DOCS**(faq): add best practice FAQ about using await with scope disposal.
|
||||||
|
- **BREAKING** **REFACTOR**(core): make closeRootScope async and await dispose.
|
||||||
|
- **BREAKING** **DOCS**(disposable): add detailed English documentation and usage examples for Disposable interface; chore: update binding_resolver and add explanatory comment in scope_test for deprecated usage.\n\n- Expanded Disposable interface docs, added sync & async example classes, and CherryPick integration sample.\n- Clarified how to implement and use Disposable in DI context.\n- Updated binding_resolver for internal improvements.\n- Added ignore for deprecated member use in scope_test for clarity and future upgrades.\n\nBREAKING CHANGE: Documentation style enhancement and clearer API usage for Disposable implementations.
|
||||||
|
|
||||||
|
## 3.0.0-dev.6
|
||||||
|
|
||||||
|
> Note: This release has breaking changes.
|
||||||
|
|
||||||
|
- **FIX**: improve global cycle detector logic.
|
||||||
|
- **DOCS**(readme): add comprehensive DI state and action logging to features.
|
||||||
|
- **DOCS**(helper): add complete DartDoc with real usage examples for CherryPick class.
|
||||||
|
- **DOCS**(log_format): add detailed English documentation for formatLogMessage function.
|
||||||
|
- **BREAKING** **FEAT**(core): refactor root scope API, improve logger injection, helpers, and tests.
|
||||||
|
- **BREAKING** **FEAT**(logger): add extensible logging API, usage examples, and bilingual documentation.
|
||||||
|
|
||||||
## 3.0.0-dev.5
|
## 3.0.0-dev.5
|
||||||
|
|
||||||
- **REFACTOR**(scope): simplify _findBindingResolver<T> with one-liner and optional chaining.
|
- **REFACTOR**(scope): simplify _findBindingResolver<T> with one-liner and optional chaining.
|
||||||
|
|||||||
@@ -192,7 +192,7 @@
|
|||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
|||||||
@@ -1,18 +1,103 @@
|
|||||||
# CherryPick
|
# CherryPick
|
||||||
|
|
||||||
`cherrypick` is a flexible and lightweight dependency injection library for Dart and Flutter. It provides an easy-to-use system for registering, scoping, and resolving dependencies using modular bindings and hierarchical scopes. The design enables cleaner architecture, testability, and modular code in your applications.
|
`cherrypick` is a flexible and lightweight dependency injection library for Dart and Flutter.
|
||||||
|
It provides an easy-to-use system for registering, scoping, and resolving dependencies using modular bindings and hierarchical scopes. The design enables cleaner architecture, testability, and modular code in your applications.
|
||||||
|
|
||||||
## Key Concepts
|
---
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
- [Key Features](#key-features)
|
||||||
|
- [Installation](#installation)
|
||||||
|
- [Getting Started](#getting-started)
|
||||||
|
- [Core Concepts](#core-concepts)
|
||||||
|
- [Binding](#binding)
|
||||||
|
- [Module](#module)
|
||||||
|
- [Scope](#scope)
|
||||||
|
- [Disposable](#disposable)
|
||||||
|
- [Dependency Resolution API](#dependency-resolution-api)
|
||||||
|
- [Using Annotations & Code Generation](#using-annotations--code-generation)
|
||||||
|
- [Advanced Features](#advanced-features)
|
||||||
|
- [Hierarchical Subscopes](#hierarchical-subscopes)
|
||||||
|
- [Logging](#logging)
|
||||||
|
- [Circular Dependency Detection](#circular-dependency-detection)
|
||||||
|
- [Performance Improvements](#performance-improvements)
|
||||||
|
- [Example Application](#example-application)
|
||||||
|
- [FAQ](#faq)
|
||||||
|
- [Documentation Links](#documentation-links)
|
||||||
|
- [Additional Modules](#additional-modules)
|
||||||
|
- [Contributing](#contributing)
|
||||||
|
- [License](#license)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Features
|
||||||
|
- Main Scope and Named Subscopes
|
||||||
|
- Named Instance Binding and Resolution
|
||||||
|
- Asynchronous and Synchronous Providers
|
||||||
|
- Providers Supporting Runtime Parameters
|
||||||
|
- Singleton Lifecycle Management
|
||||||
|
- Modular and Hierarchical Composition
|
||||||
|
- Null-safe Resolution (tryResolve/tryResolveAsync)
|
||||||
|
- Circular Dependency Detection (Local and Global)
|
||||||
|
- Comprehensive logging of dependency injection state and actions
|
||||||
|
- Automatic resource cleanup for all registered Disposable dependencies
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Add to your `pubspec.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
dependencies:
|
||||||
|
cherrypick: ^<latest_version>
|
||||||
|
````
|
||||||
|
|
||||||
|
Then run:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
dart pub get
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
Here is a minimal example that registers and resolves a dependency:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
|
||||||
|
class AppModule extends Module {
|
||||||
|
@override
|
||||||
|
void builder(Scope currentScope) {
|
||||||
|
bind<ApiClient>().toInstance(ApiClientMock());
|
||||||
|
bind<String>().toProvide(() => "Hello, CherryPick!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final rootScope = CherryPick.openRootScope();
|
||||||
|
rootScope.installModules([AppModule()]);
|
||||||
|
|
||||||
|
final greeting = rootScope.resolve<String>();
|
||||||
|
print(greeting); // prints: Hello, CherryPick!
|
||||||
|
|
||||||
|
await CherryPick.closeRootScope();
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Core Concepts
|
||||||
|
|
||||||
### Binding
|
### Binding
|
||||||
|
|
||||||
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
|
||||||
- Named instances for resolving by string key
|
* Named instances for resolving by string key
|
||||||
- Optional singleton lifecycle
|
* Optional singleton lifecycle
|
||||||
|
|
||||||
#### Example
|
#### Example
|
||||||
|
|
||||||
@@ -79,29 +164,67 @@ final str = rootScope.resolve<String>();
|
|||||||
// Resolve a dependency asynchronously
|
// Resolve a dependency asynchronously
|
||||||
final result = await rootScope.resolveAsync<String>();
|
final result = await rootScope.resolveAsync<String>();
|
||||||
|
|
||||||
// Close the root scope once done
|
// Recommended: Close the root scope and release all resources
|
||||||
CherryPick.closeRootScope();
|
await CherryPick.closeRootScope();
|
||||||
|
|
||||||
|
// Alternatively, you may manually call dispose on any scope you manage individually
|
||||||
|
// await rootScope.dispose();
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Working with Subscopes
|
---
|
||||||
|
|
||||||
|
### Disposable
|
||||||
|
|
||||||
|
CherryPick can automatically clean up any dependency that implements the `Disposable` interface. This makes resource management (for controllers, streams, sockets, files, etc.) easy and reliable—especially when scopes or the app are shut down.
|
||||||
|
|
||||||
|
If you bind an object implementing `Disposable` as a singleton or provide it via the DI container, CherryPick will call its `dispose()` method when the scope is closed or cleaned up.
|
||||||
|
|
||||||
|
#### Key Points
|
||||||
|
- Supports both synchronous and asynchronous cleanup (dispose may return `void` or `Future`).
|
||||||
|
- All `Disposable` instances from the current scope and subscopes will be disposed in the correct order.
|
||||||
|
- Prevents resource leaks and enforces robust cleanup.
|
||||||
|
- No manual wiring needed once your class implements `Disposable`.
|
||||||
|
|
||||||
|
#### Minimal Sync Example
|
||||||
```dart
|
```dart
|
||||||
// Open a named child scope (e.g., for a feature/module)
|
class CacheManager implements Disposable {
|
||||||
final subScope = rootScope.openSubScope('featureScope')
|
void dispose() {
|
||||||
..installModules([FeatureModule()]);
|
cache.clear();
|
||||||
|
print('CacheManager disposed!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Resolve from subScope, with fallback to parents if missing
|
final scope = CherryPick.openRootScope();
|
||||||
final dataBloc = await subScope.resolveAsync<DataBloc>();
|
scope.installModules([
|
||||||
|
Module((bind) => bind<CacheManager>().toProvide(() => CacheManager()).singleton()),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// ...later
|
||||||
|
await CherryPick.closeRootScope(); // prints: CacheManager disposed!
|
||||||
```
|
```
|
||||||
|
|
||||||
### Fast Dependency Lookup (Performance Improvement)
|
#### Async Example
|
||||||
|
```dart
|
||||||
|
class MyServiceWithSocket implements Disposable {
|
||||||
|
@override
|
||||||
|
Future<void> dispose() async {
|
||||||
|
await socket.close();
|
||||||
|
print('Socket closed!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
> **Performance Note:**
|
scope.installModules([
|
||||||
> **Starting from version 3.0.0**, CherryPick uses a Map-based resolver index for dependency lookup. This means calls to `resolve<T>()` and related methods are now O(1) operations, regardless of the number of modules or bindings in your scope. Previously, the library had to iterate over all modules and bindings to locate the requested dependency, which could impact performance as your project grew.
|
Module((bind) => bind<MyServiceWithSocket>().toProvide(() => MyServiceWithSocket()).singleton()),
|
||||||
>
|
]);
|
||||||
> This optimization is internal and does not change any library APIs or usage patterns, but it significantly improves resolution speed in larger applications.
|
|
||||||
|
|
||||||
### Dependency Lookup API
|
await CherryPick.closeRootScope(); // awaits async disposal
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tip:** Always call `await CherryPick.closeRootScope()` or `await scope.closeSubScope(key)` in your shutdown/teardown logic to ensure all resources are released automatically.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dependency Resolution API
|
||||||
|
|
||||||
- `resolve<T>()` — Locates a dependency instance or throws if missing.
|
- `resolve<T>()` — Locates a dependency instance or throws if missing.
|
||||||
- `resolveAsync<T>()` — Async variant for dependencies requiring async binding.
|
- `resolveAsync<T>()` — Async variant for dependencies requiring async binding.
|
||||||
@@ -113,6 +236,335 @@ Supports:
|
|||||||
- Named dependencies
|
- Named dependencies
|
||||||
- Provider functions with and without runtime parameters
|
- Provider functions with and without runtime parameters
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Using Annotations & Code Generation
|
||||||
|
|
||||||
|
CherryPick provides best-in-class developer ergonomics and type safety through **Dart annotations** and code generation. This lets you dramatically reduce boilerplate: simply annotate your classes, fields, and modules, run the code generator, and enjoy auto-wired dependency injection!
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
|
||||||
|
1. **Annotate** your services, providers, and fields using `cherrypick_annotations`.
|
||||||
|
2. **Generate** code using `cherrypick_generator` with `build_runner`.
|
||||||
|
3. **Use** generated modules and mixins for fully automated DI (dependency injection).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Supported Annotations
|
||||||
|
|
||||||
|
| Annotation | Target | Description |
|
||||||
|
|-------------------|---------------|--------------------------------------------------------------------------------|
|
||||||
|
| `@injectable()` | class | Enables automatic field injection for this class (mixin will be generated) |
|
||||||
|
| `@inject()` | field | Field will be injected using DI (works with @injectable classes) |
|
||||||
|
| `@module()` | class | Declares a DI module; its methods can provide services/providers |
|
||||||
|
| `@provide` | method | Registers as a DI provider method (may have dependencies as parameters) |
|
||||||
|
| `@instance` | method/class | Registers an instance (new object on each resolution, i.e. factory) |
|
||||||
|
| `@singleton` | method/class | Registers as a singleton (one instance per scope) |
|
||||||
|
| `@named` | field/param | Use named instance (bind/resolve by name or apply to field/param) |
|
||||||
|
| `@scope` | field/param | Inject or resolve from a specific named scope |
|
||||||
|
| `@params` | param | Marks method parameter as filled by user-supplied runtime params at resolution |
|
||||||
|
|
||||||
|
You can easily **combine** these annotations for advanced scenarios!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Field Injection Example
|
||||||
|
|
||||||
|
```dart
|
||||||
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
class ProfilePage with _\$ProfilePage {
|
||||||
|
@inject()
|
||||||
|
late final AuthService auth;
|
||||||
|
|
||||||
|
@inject()
|
||||||
|
@scope('profile')
|
||||||
|
late final ProfileManager manager;
|
||||||
|
|
||||||
|
@inject()
|
||||||
|
@named('admin')
|
||||||
|
late final UserService adminUserService;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- After running build_runner, the mixin `_ProfilePage` will be generated for field injection.
|
||||||
|
- Call `myProfilePage.injectFields();` or use the mixin's auto-inject feature, and all dependencies will be set up for you.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Module and Provider Example
|
||||||
|
|
||||||
|
```dart
|
||||||
|
@module()
|
||||||
|
abstract class AppModule {
|
||||||
|
@singleton
|
||||||
|
AuthService provideAuth(Api api) => AuthService(api);
|
||||||
|
|
||||||
|
@named('logging')
|
||||||
|
@provide
|
||||||
|
Future<Logger> provideLogger(@params Map<String, dynamic> args) async => ...;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Mark class as `@module`, write provider methods.
|
||||||
|
- Use `@singleton`, `@named`, `@provide`, `@params` to control lifecycle, key names, and parameters.
|
||||||
|
- The generator will produce a class like `$AppModule` with the proper DI bindings.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Usage Steps
|
||||||
|
|
||||||
|
1. **Add to your pubspec.yaml**:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
dependencies:
|
||||||
|
cherrypick: any
|
||||||
|
cherrypick_annotations: any
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
cherrypick_generator: any
|
||||||
|
build_runner: any
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Annotate** your classes and modules as above.
|
||||||
|
|
||||||
|
3. **Run code generation:**
|
||||||
|
|
||||||
|
```shell
|
||||||
|
dart run build_runner build --delete-conflicting-outputs
|
||||||
|
# or in Flutter:
|
||||||
|
flutter pub run build_runner build --delete-conflicting-outputs
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Register modules and use auto-injection:**
|
||||||
|
|
||||||
|
```dart
|
||||||
|
final scope = CherryPick.openRootScope()
|
||||||
|
..installModules([$AppModule()]);
|
||||||
|
|
||||||
|
final profile = ProfilePage();
|
||||||
|
profile.injectFields(); // injects all @inject fields
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Advanced: Parameters, Named Instances, and Scopes
|
||||||
|
|
||||||
|
- Use `@named` for key-based multi-implementation injection.
|
||||||
|
- Use `@scope` when dependencies live in a non-root scope.
|
||||||
|
- Use `@params` for runtime arguments passed during resolution.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Troubleshooting & Tips
|
||||||
|
|
||||||
|
- After modifying DI-related code, always re-run `build_runner`.
|
||||||
|
- Do not manually edit `.g.dart` files—let the generator manage them.
|
||||||
|
- Errors in annotation usage (e.g., using `@singleton` on wrong target) are shown at build time.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### References
|
||||||
|
|
||||||
|
- [Full annotation reference (en)](doc/annotations_en.md)
|
||||||
|
- [cherrypick_annotations/README.md](../cherrypick_annotations/README.md)
|
||||||
|
- [cherrypick_generator/README.md](../cherrypick_generator/README.md)
|
||||||
|
- See the [`examples/postly`](../examples/postly) for a full working DI+annotations app.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Advanced Features
|
||||||
|
|
||||||
|
### Hierarchical Subscopes
|
||||||
|
|
||||||
|
CherryPick supports a hierarchical structure of scopes, allowing you to create complex and modular dependency graphs for advanced application architectures. Each subscope inherits from its parent, enabling context-specific overrides while still allowing access to global or shared services.
|
||||||
|
|
||||||
|
#### Key Points
|
||||||
|
|
||||||
|
- **Subscopes** are child scopes that can be opened from any existing scope (including the root).
|
||||||
|
- Dependencies registered in a subscope override those from parent scopes when resolved.
|
||||||
|
- If a dependency is not found in the current subscope, the resolution process automatically searches parent scopes up the hierarchy.
|
||||||
|
- Subscopes can have their own modules, lifetime, and disposable objects.
|
||||||
|
- You can nest subscopes to any depth, modeling features, flows, or components independently.
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
```dart
|
||||||
|
final rootScope = CherryPick.openRootScope();
|
||||||
|
rootScope.installModules([AppModule()]);
|
||||||
|
|
||||||
|
// Open a hierarchical subscope for a feature or page
|
||||||
|
final userFeatureScope = rootScope.openSubScope('userFeature');
|
||||||
|
userFeatureScope.installModules([UserFeatureModule()]);
|
||||||
|
|
||||||
|
// Dependencies defined in UserFeatureModule will take precedence
|
||||||
|
final userService = userFeatureScope.resolve<UserService>();
|
||||||
|
|
||||||
|
// If not found in the subscope, lookup continues in the parent (rootScope)
|
||||||
|
final sharedService = userFeatureScope.resolve<SharedService>();
|
||||||
|
|
||||||
|
// You can nest subscopes
|
||||||
|
final dialogScope = userFeatureScope.openSubScope('dialog');
|
||||||
|
dialogScope.installModules([DialogModule()]);
|
||||||
|
final dialogManager = dialogScope.resolve<DialogManager>();
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Use Cases
|
||||||
|
|
||||||
|
- Isolate feature modules, flows, or screens with their own dependencies.
|
||||||
|
- Provide and override services for specific navigation stacks or platform-specific branches.
|
||||||
|
- Manage the lifetime and disposal of groups of dependencies independently (e.g., per-user, per-session, per-component).
|
||||||
|
|
||||||
|
**Tip:** Always close subscopes when they are no longer needed to release resources and trigger cleanup of Disposable dependencies.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Logging
|
||||||
|
|
||||||
|
CherryPick lets you log all dependency injection (DI) events and errors using a flexible observer mechanism.
|
||||||
|
|
||||||
|
#### Custom Observers
|
||||||
|
|
||||||
|
You can pass any implementation of `CherryPickObserver` to your root scope or any sub-scope.
|
||||||
|
This allows centralized and extensible logging, which you can direct to print, files, visualization frameworks, external loggers, or systems like [Talker](https://pub.dev/packages/talker).
|
||||||
|
|
||||||
|
##### Example: Printing All Events
|
||||||
|
|
||||||
|
```dart
|
||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// Use the built-in PrintCherryPickObserver for console logs
|
||||||
|
final observer = PrintCherryPickObserver();
|
||||||
|
final scope = CherryPick.openRootScope(observer: observer);
|
||||||
|
// All DI actions and errors will now be printed!
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Example: Advanced Logging with Talker
|
||||||
|
|
||||||
|
For richer logging, analytics, or UI overlays, use an advanced observer such as [talker_cherrypick_logger](../talker_cherrypick_logger):
|
||||||
|
|
||||||
|
```dart
|
||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
import 'package:talker/talker.dart';
|
||||||
|
import 'package:talker_cherrypick_logger/talker_cherrypick_logger.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
final talker = Talker();
|
||||||
|
final observer = TalkerCherryPickObserver(talker);
|
||||||
|
CherryPick.openRootScope(observer: observer);
|
||||||
|
// All container events go to the Talker log system!
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Default Behavior
|
||||||
|
|
||||||
|
- By default, logging is silent (`SilentCherryPickObserver`) for production, with no output unless you supply an observer.
|
||||||
|
- You can configure observers **per scope** for isolated, test-specific, or feature-specific logging.
|
||||||
|
|
||||||
|
#### Observer Capabilities
|
||||||
|
|
||||||
|
Events you can observe and log:
|
||||||
|
- Dependency registration
|
||||||
|
- Instance requests, creations, disposals
|
||||||
|
- Module installs/removals
|
||||||
|
- Scope opening/closing
|
||||||
|
- Cache hits/misses
|
||||||
|
- Cycle detection
|
||||||
|
- Diagnostics, warnings, errors
|
||||||
|
|
||||||
|
Just implement or extend `CherryPickObserver` and direct messages anywhere you want!
|
||||||
|
|
||||||
|
#### When to Use
|
||||||
|
|
||||||
|
- Enable verbose logging and debugging in development or test builds.
|
||||||
|
- Route logs to your main log system or analytics.
|
||||||
|
- Hook into DI lifecycle for profiling or monitoring.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 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)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
> **Performance Note:**
|
||||||
|
> **Starting from version 3.0.0**, CherryPick uses a Map-based resolver index for dependency lookup. This means calls to `resolve<T>()` and related methods are now O(1) operations, regardless of the number of modules or bindings in your scope. Previously, the library had to iterate over all modules and bindings to locate the requested dependency, which could impact performance as your project grew.
|
||||||
|
>
|
||||||
|
> This optimization is internal and does not change any library APIs or usage patterns, but it significantly improves resolution speed in larger applications.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Example Application
|
## Example Application
|
||||||
|
|
||||||
Below is a complete example illustrating modules, subscopes, async providers, and dependency resolution.
|
Below is a complete example illustrating modules, subscopes, async providers, and dependency resolution.
|
||||||
@@ -234,102 +686,51 @@ class ApiClientImpl implements ApiClient {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Features
|
---
|
||||||
|
|
||||||
- [x] Main Scope and Named Subscopes
|
## FAQ
|
||||||
- [x] Named Instance Binding and Resolution
|
|
||||||
- [x] Asynchronous and Synchronous Providers
|
|
||||||
- [x] Providers Supporting Runtime Parameters
|
|
||||||
- [x] Singleton Lifecycle Management
|
|
||||||
- [x] Modular and Hierarchical Composition
|
|
||||||
- [x] Null-safe Resolution (tryResolve/tryResolveAsync)
|
|
||||||
- [x] Circular Dependency Detection (Local and Global)
|
|
||||||
|
|
||||||
## Quick Guide: Circular Dependency Detection
|
### Q: Do I need to use `await` with CherryPick.closeRootScope(), CherryPick.closeScope(), or scope.dispose() if I have no Disposable services?
|
||||||
|
|
||||||
CherryPick can detect circular dependencies in your DI configuration, helping you avoid infinite loops and hard-to-debug errors.
|
**A:**
|
||||||
|
Yes! Even if none of your services currently implement `Disposable`, always use `await` when closing scopes. If you later add resource cleanup (by implementing `dispose()`), CherryPick will handle it automatically without you needing to change your scope cleanup code. This ensures resource management is future-proof, robust, and covers all application scenarios.
|
||||||
|
|
||||||
**How to use:**
|
---
|
||||||
|
|
||||||
### 1. Enable Cycle Detection for Development
|
## Documentation Links
|
||||||
|
|
||||||
**Local detection (within one scope):**
|
* Circular Dependency Detection [(En)](doc/cycle_detection.en.md)[(Ru)](doc/cycle_detection.ru.md)
|
||||||
```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
|
## Additional Modules
|
||||||
|
|
||||||
If you declare mutually dependent services:
|
CherryPick provides a set of official add-on modules for advanced use cases and specific platforms:
|
||||||
```dart
|
|
||||||
class A { A(B b); }
|
|
||||||
class B { B(A a); }
|
|
||||||
|
|
||||||
scope.installModules([
|
| Module name | Description | Documentation |
|
||||||
Module((bind) {
|
|-------------|-------------|---------------|
|
||||||
bind<A>().to((s) => A(s.resolve<B>()));
|
| [**cherrypick_annotations**](https://pub.dev/packages/cherrypick_annotations) | Dart annotations for concise DI definitions and code generation. | [README](../cherrypick_annotations/README.md) |
|
||||||
bind<B>().to((s) => B(s.resolve<A>()));
|
| [**cherrypick_generator**](https://pub.dev/packages/cherrypick_generator) | Code generator to produce DI bindings based on annotations. | [README](../cherrypick_generator/README.md) |
|
||||||
}),
|
| [**cherrypick_flutter**](https://pub.dev/packages/cherrypick_flutter) | Flutter integration: DI provider widgets and helpers for Flutter. | [README](../cherrypick_flutter/README.md) |
|
||||||
]);
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
- [Circular Dependency Detection (English)](doc/cycle_detection.en.md)
|
|
||||||
- [Обнаружение циклических зависимостей (Русский)](doc/cycle_detection.ru.md)
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Contributions are welcome! Please open issues or submit pull requests on [GitHub](https://github.com/pese-git/cherrypick).
|
Contributions are welcome! Please open issues or submit pull requests on [GitHub](https://github.com/pese-git/cherrypick).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Licensed under the [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0).
|
Licensed under the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Important:** 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 specific language governing permissions and limitations under the License.
|
**Important:** 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 specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Links
|
## Links
|
||||||
|
|
||||||
- [GitHub Repository](https://github.com/pese-git/cherrypick)
|
- [GitHub Repository](https://github.com/pese-git/cherrypick)
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ class FeatureModule extends Module {
|
|||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
try {
|
try {
|
||||||
final scope = openRootScope().installModules([AppModule()]);
|
final scope = CherryPick.openRootScope().installModules([AppModule()]);
|
||||||
|
|
||||||
final subScope = scope
|
final subScope = scope
|
||||||
.openSubScope("featureScope")
|
.openSubScope("featureScope")
|
||||||
|
|||||||
37
cherrypick/example/cherrypick_logger_demo.dart
Normal file
37
cherrypick/example/cherrypick_logger_demo.dart
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
|
||||||
|
/// Example of a simple service class.
|
||||||
|
class UserRepository {
|
||||||
|
String getUserName() => 'Sergey DI';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// DI module for registering dependencies.
|
||||||
|
class AppModule extends Module {
|
||||||
|
@override
|
||||||
|
void builder(Scope currentScope) {
|
||||||
|
bind<UserRepository>().toInstance(UserRepository());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// Set a global logger for the DI system
|
||||||
|
CherryPick.setGlobalObserver(PrintCherryPickObserver());
|
||||||
|
|
||||||
|
// Open the root scope
|
||||||
|
final rootScope = CherryPick.openRootScope();
|
||||||
|
|
||||||
|
// Register the DI module
|
||||||
|
rootScope.installModules([AppModule()]);
|
||||||
|
|
||||||
|
// Resolve a dependency (service)
|
||||||
|
final repo = rootScope.resolve<UserRepository>();
|
||||||
|
print('User: ${repo.getUserName()}');
|
||||||
|
|
||||||
|
// Work with a sub-scope (create/close)
|
||||||
|
final subScope = rootScope.openSubScope('feature.profile');
|
||||||
|
subScope.closeSubScope('feature.profile');
|
||||||
|
|
||||||
|
// Demonstrate disabling and re-enabling logging
|
||||||
|
CherryPick.setGlobalObserver(SilentCherryPickObserver());
|
||||||
|
rootScope.resolve<UserRepository>(); // now without logs
|
||||||
|
}
|
||||||
@@ -126,7 +126,7 @@ void main() {
|
|||||||
// Example 1: Demonstrate circular dependency
|
// Example 1: Demonstrate circular dependency
|
||||||
print('1. Attempt to create a scope with circular dependencies:');
|
print('1. Attempt to create a scope with circular dependencies:');
|
||||||
try {
|
try {
|
||||||
final scope = Scope(null);
|
final scope = CherryPick.openRootScope();
|
||||||
scope.enableCycleDetection(); // Включаем обнаружение циклических зависимостей
|
scope.enableCycleDetection(); // Включаем обнаружение циклических зависимостей
|
||||||
|
|
||||||
scope.installModules([
|
scope.installModules([
|
||||||
@@ -144,7 +144,7 @@ void main() {
|
|||||||
// Example 2: Without circular dependency detection (dangerous!)
|
// Example 2: Without circular dependency detection (dangerous!)
|
||||||
print('2. Same code without circular dependency detection:');
|
print('2. Same code without circular dependency detection:');
|
||||||
try {
|
try {
|
||||||
final scope = Scope(null);
|
final scope = CherryPick.openRootScope();
|
||||||
// НЕ включаем обнаружение циклических зависимостей
|
// НЕ включаем обнаружение циклических зависимостей
|
||||||
|
|
||||||
scope.installModules([
|
scope.installModules([
|
||||||
@@ -166,7 +166,7 @@ void main() {
|
|||||||
// Example 3: Correct architecture without circular dependencies
|
// Example 3: Correct architecture without circular dependencies
|
||||||
print('3. Correct architecture without circular dependencies:');
|
print('3. Correct architecture without circular dependencies:');
|
||||||
try {
|
try {
|
||||||
final scope = Scope(null);
|
final scope = CherryPick.openRootScope();
|
||||||
scope.enableCycleDetection(); // Включаем для безопасности
|
scope.enableCycleDetection(); // Включаем для безопасности
|
||||||
|
|
||||||
scope.installModules([
|
scope.installModules([
|
||||||
|
|||||||
40
cherrypick/example/disposable_example.dart
Normal file
40
cherrypick/example/disposable_example.dart
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
|
||||||
|
/// Ваш сервис с освобождением ресурсов
|
||||||
|
class MyService implements Disposable {
|
||||||
|
bool wasDisposed = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
// Например: закрыть соединение, остановить таймер, освободить память
|
||||||
|
wasDisposed = true;
|
||||||
|
print('MyService disposed!');
|
||||||
|
}
|
||||||
|
|
||||||
|
void doSomething() => print('Doing something...');
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
final scope = CherryPick.openRootScope();
|
||||||
|
|
||||||
|
// Регистрируем биндинг (singleton для примера)
|
||||||
|
scope.installModules([
|
||||||
|
ModuleImpl(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Получаем зависимость
|
||||||
|
final service = scope.resolve<MyService>();
|
||||||
|
service.doSomething(); // «Doing something...»
|
||||||
|
|
||||||
|
// Освобождаем все ресурсы
|
||||||
|
scope.dispose();
|
||||||
|
print('Service wasDisposed = ${service.wasDisposed}'); // true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Пример модуля CherryPick
|
||||||
|
class ModuleImpl extends Module {
|
||||||
|
@override
|
||||||
|
void builder(Scope scope) {
|
||||||
|
bind<MyService>().toProvide(() => MyService()).singleton();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ library;
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
@@ -20,3 +20,5 @@ export 'package:cherrypick/src/global_cycle_detector.dart';
|
|||||||
export 'package:cherrypick/src/helper.dart';
|
export 'package:cherrypick/src/helper.dart';
|
||||||
export 'package:cherrypick/src/module.dart';
|
export 'package:cherrypick/src/module.dart';
|
||||||
export 'package:cherrypick/src/scope.dart';
|
export 'package:cherrypick/src/scope.dart';
|
||||||
|
export 'package:cherrypick/src/disposable.dart';
|
||||||
|
export 'package:cherrypick/src/observer.dart';
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
@@ -13,17 +13,73 @@
|
|||||||
|
|
||||||
import 'package:cherrypick/src/binding_resolver.dart';
|
import 'package:cherrypick/src/binding_resolver.dart';
|
||||||
|
|
||||||
/// 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.
|
||||||
///
|
///
|
||||||
|
import 'package:cherrypick/src/observer.dart';
|
||||||
|
|
||||||
class Binding<T> {
|
class Binding<T> {
|
||||||
late Type _key;
|
late Type _key;
|
||||||
String? _name;
|
String? _name;
|
||||||
|
|
||||||
BindingResolver<T>? _resolver;
|
BindingResolver<T>? _resolver;
|
||||||
|
|
||||||
Binding() {
|
CherryPickObserver? observer;
|
||||||
|
|
||||||
|
// Deferred logging flags
|
||||||
|
bool _createdLogged = false;
|
||||||
|
bool _namedLogged = false;
|
||||||
|
bool _singletonLogged = false;
|
||||||
|
|
||||||
|
Binding({this.observer}) {
|
||||||
_key = T;
|
_key = T;
|
||||||
|
// Deferred уведомения observer, не логировать здесь напрямую
|
||||||
|
}
|
||||||
|
|
||||||
|
void markCreated() {
|
||||||
|
if (!_createdLogged) {
|
||||||
|
observer?.onBindingRegistered(
|
||||||
|
runtimeType.toString(),
|
||||||
|
T,
|
||||||
|
);
|
||||||
|
_createdLogged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void markNamed() {
|
||||||
|
if (isNamed && !_namedLogged) {
|
||||||
|
observer?.onDiagnostic(
|
||||||
|
'Binding named: ${T.toString()} name: $_name',
|
||||||
|
details: {
|
||||||
|
'type': 'Binding',
|
||||||
|
'name': T.toString(),
|
||||||
|
'nameParam': _name,
|
||||||
|
'description': 'named',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
_namedLogged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void markSingleton() {
|
||||||
|
if (isSingleton && !_singletonLogged) {
|
||||||
|
observer?.onDiagnostic(
|
||||||
|
'Binding singleton: ${T.toString()}${_name != null ? ' name: $_name' : ''}',
|
||||||
|
details: {
|
||||||
|
'type': 'Binding',
|
||||||
|
'name': T.toString(),
|
||||||
|
if (_name != null) 'name': _name,
|
||||||
|
'description': 'singleton mode enabled',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
_singletonLogged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void logAllDeferred() {
|
||||||
|
markCreated();
|
||||||
|
markNamed();
|
||||||
|
markSingleton();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Метод возвращает тип экземпляра.
|
/// RU: Метод возвращает тип экземпляра.
|
||||||
@@ -58,6 +114,7 @@ class Binding<T> {
|
|||||||
/// return [Binding]
|
/// return [Binding]
|
||||||
Binding<T> withName(String name) {
|
Binding<T> withName(String name) {
|
||||||
_name = name;
|
_name = name;
|
||||||
|
// Не логируем здесь, deferred log via markNamed()
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +124,6 @@ class Binding<T> {
|
|||||||
/// return [Binding]
|
/// return [Binding]
|
||||||
Binding<T> toInstance(Instance<T> value) {
|
Binding<T> toInstance(Instance<T> value) {
|
||||||
_resolver = InstanceResolver<T>(value);
|
_resolver = InstanceResolver<T>(value);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +133,6 @@ 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);
|
_resolver = ProviderResolver<T>((_) => value.call(), withParams: false);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +142,6 @@ 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);
|
_resolver = ProviderResolver<T>(value, withParams: true);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,15 +166,61 @@ class Binding<T> {
|
|||||||
/// return [Binding]
|
/// return [Binding]
|
||||||
Binding<T> singleton() {
|
Binding<T> singleton() {
|
||||||
_resolver?.toSingleton();
|
_resolver?.toSingleton();
|
||||||
|
// Не логируем здесь, deferred log via markSingleton()
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
T? resolveSync([dynamic params]) {
|
T? resolveSync([dynamic params]) {
|
||||||
return resolver?.resolveSync(params);
|
final res = resolver?.resolveSync(params);
|
||||||
|
if (res != null) {
|
||||||
|
observer?.onDiagnostic(
|
||||||
|
'Binding resolved instance: ${T.toString()}',
|
||||||
|
details: {
|
||||||
|
if (_name != null) 'name': _name,
|
||||||
|
'method': 'resolveSync',
|
||||||
|
'description': 'object created/resolved',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
observer?.onWarning(
|
||||||
|
'resolveSync returned null: ${T.toString()}',
|
||||||
|
details: {
|
||||||
|
if (_name != null) 'name': _name,
|
||||||
|
'method': 'resolveSync',
|
||||||
|
'description': 'resolveSync returned null',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<T>? resolveAsync([dynamic params]) {
|
Future<T>? resolveAsync([dynamic params]) {
|
||||||
return resolver?.resolveAsync(params);
|
final future = resolver?.resolveAsync(params);
|
||||||
|
if (future != null) {
|
||||||
|
future
|
||||||
|
.then((res) => observer?.onDiagnostic(
|
||||||
|
'Future resolved for: ${T.toString()}',
|
||||||
|
details: {
|
||||||
|
if (_name != null) 'name': _name,
|
||||||
|
'method': 'resolveAsync',
|
||||||
|
'description': 'Future resolved',
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.catchError((e, s) => observer?.onError(
|
||||||
|
'resolveAsync error: ${T.toString()}',
|
||||||
|
e,
|
||||||
|
s,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
observer?.onWarning(
|
||||||
|
'resolveAsync returned null: ${T.toString()}',
|
||||||
|
details: {
|
||||||
|
if (_name != null) 'name': _name,
|
||||||
|
'method': 'resolveAsync',
|
||||||
|
'description': 'resolveAsync returned null',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return future;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
//
|
||||||
|
// 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
|
||||||
|
// https://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:async';
|
import 'dart:async';
|
||||||
|
|
||||||
typedef Instance<T> = FutureOr<T>;
|
typedef Instance<T> = FutureOr<T>;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
|
import 'package:cherrypick/src/observer.dart';
|
||||||
|
|
||||||
/// RU: Исключение, выбрасываемое при обнаружении циклической зависимости.
|
/// RU: Исключение, выбрасываемое при обнаружении циклической зависимости.
|
||||||
/// ENG: Exception thrown when a circular dependency is detected.
|
/// ENG: Exception thrown when a circular dependency is detected.
|
||||||
@@ -19,7 +20,10 @@ class CircularDependencyException implements Exception {
|
|||||||
final String message;
|
final String message;
|
||||||
final List<String> dependencyChain;
|
final List<String> dependencyChain;
|
||||||
|
|
||||||
const CircularDependencyException(this.message, this.dependencyChain);
|
CircularDependencyException(this.message, this.dependencyChain) {
|
||||||
|
// DEBUG
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
@@ -31,24 +35,36 @@ class CircularDependencyException implements Exception {
|
|||||||
/// RU: Детектор циклических зависимостей для CherryPick DI контейнера.
|
/// RU: Детектор циклических зависимостей для CherryPick DI контейнера.
|
||||||
/// ENG: Circular dependency detector for CherryPick DI container.
|
/// ENG: Circular dependency detector for CherryPick DI container.
|
||||||
class CycleDetector {
|
class CycleDetector {
|
||||||
// Стек текущих разрешаемых зависимостей
|
final CherryPickObserver _observer;
|
||||||
final Set<String> _resolutionStack = HashSet<String>();
|
final Set<String> _resolutionStack = HashSet<String>();
|
||||||
|
|
||||||
// История разрешения для построения цепочки зависимостей
|
|
||||||
final List<String> _resolutionHistory = [];
|
final List<String> _resolutionHistory = [];
|
||||||
|
|
||||||
|
CycleDetector({required CherryPickObserver observer}) : _observer = observer;
|
||||||
|
|
||||||
/// RU: Начинает отслеживание разрешения зависимости.
|
/// RU: Начинает отслеживание разрешения зависимости.
|
||||||
/// ENG: Starts tracking dependency resolution.
|
/// ENG: Starts tracking dependency resolution.
|
||||||
///
|
///
|
||||||
/// Throws [CircularDependencyException] if circular dependency is detected.
|
/// Throws [CircularDependencyException] if circular dependency is detected.
|
||||||
void startResolving<T>({String? named}) {
|
void startResolving<T>({String? named}) {
|
||||||
final dependencyKey = _createDependencyKey<T>(named);
|
final dependencyKey = _createDependencyKey<T>(named);
|
||||||
|
_observer.onDiagnostic(
|
||||||
|
'CycleDetector startResolving: $dependencyKey',
|
||||||
|
details: {
|
||||||
|
'event': 'startResolving',
|
||||||
|
'stackSize': _resolutionStack.length,
|
||||||
|
},
|
||||||
|
);
|
||||||
if (_resolutionStack.contains(dependencyKey)) {
|
if (_resolutionStack.contains(dependencyKey)) {
|
||||||
// Найдена циклическая зависимость
|
|
||||||
final cycleStartIndex = _resolutionHistory.indexOf(dependencyKey);
|
final cycleStartIndex = _resolutionHistory.indexOf(dependencyKey);
|
||||||
final cycle = _resolutionHistory.sublist(cycleStartIndex)..add(dependencyKey);
|
final cycle = _resolutionHistory.sublist(cycleStartIndex)..add(dependencyKey);
|
||||||
|
_observer.onCycleDetected(
|
||||||
|
cycle,
|
||||||
|
);
|
||||||
|
_observer.onError(
|
||||||
|
'Cycle detected for $dependencyKey',
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
);
|
||||||
throw CircularDependencyException(
|
throw CircularDependencyException(
|
||||||
'Circular dependency detected for $dependencyKey',
|
'Circular dependency detected for $dependencyKey',
|
||||||
cycle,
|
cycle,
|
||||||
@@ -63,8 +79,11 @@ class CycleDetector {
|
|||||||
/// ENG: Finishes tracking dependency resolution.
|
/// ENG: Finishes tracking dependency resolution.
|
||||||
void finishResolving<T>({String? named}) {
|
void finishResolving<T>({String? named}) {
|
||||||
final dependencyKey = _createDependencyKey<T>(named);
|
final dependencyKey = _createDependencyKey<T>(named);
|
||||||
|
_observer.onDiagnostic(
|
||||||
|
'CycleDetector finishResolving: $dependencyKey',
|
||||||
|
details: {'event': 'finishResolving'},
|
||||||
|
);
|
||||||
_resolutionStack.remove(dependencyKey);
|
_resolutionStack.remove(dependencyKey);
|
||||||
|
|
||||||
// Удаляем из истории только если это последний элемент
|
// Удаляем из истории только если это последний элемент
|
||||||
if (_resolutionHistory.isNotEmpty &&
|
if (_resolutionHistory.isNotEmpty &&
|
||||||
_resolutionHistory.last == dependencyKey) {
|
_resolutionHistory.last == dependencyKey) {
|
||||||
@@ -75,6 +94,13 @@ class CycleDetector {
|
|||||||
/// RU: Очищает все состояние детектора.
|
/// RU: Очищает все состояние детектора.
|
||||||
/// ENG: Clears all detector state.
|
/// ENG: Clears all detector state.
|
||||||
void clear() {
|
void clear() {
|
||||||
|
_observer.onDiagnostic(
|
||||||
|
'CycleDetector clear',
|
||||||
|
details: {
|
||||||
|
'event': 'clear',
|
||||||
|
'description': 'resolution stack cleared',
|
||||||
|
},
|
||||||
|
);
|
||||||
_resolutionStack.clear();
|
_resolutionStack.clear();
|
||||||
_resolutionHistory.clear();
|
_resolutionHistory.clear();
|
||||||
}
|
}
|
||||||
@@ -102,17 +128,32 @@ class CycleDetector {
|
|||||||
/// ENG: Mixin for adding circular dependency detection support.
|
/// ENG: Mixin for adding circular dependency detection support.
|
||||||
mixin CycleDetectionMixin {
|
mixin CycleDetectionMixin {
|
||||||
CycleDetector? _cycleDetector;
|
CycleDetector? _cycleDetector;
|
||||||
|
CherryPickObserver get observer;
|
||||||
|
|
||||||
/// RU: Включает обнаружение циклических зависимостей.
|
/// RU: Включает обнаружение циклических зависимостей.
|
||||||
/// ENG: Enables circular dependency detection.
|
/// ENG: Enables circular dependency detection.
|
||||||
void enableCycleDetection() {
|
void enableCycleDetection() {
|
||||||
_cycleDetector = CycleDetector();
|
_cycleDetector = CycleDetector(observer: observer);
|
||||||
|
observer.onDiagnostic(
|
||||||
|
'CycleDetection enabled',
|
||||||
|
details: {
|
||||||
|
'event': 'enable',
|
||||||
|
'description': 'cycle detection enabled',
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Отключает обнаружение циклических зависимостей.
|
/// RU: Отключает обнаружение циклических зависимостей.
|
||||||
/// ENG: Disables circular dependency detection.
|
/// ENG: Disables circular dependency detection.
|
||||||
void disableCycleDetection() {
|
void disableCycleDetection() {
|
||||||
_cycleDetector?.clear();
|
_cycleDetector?.clear();
|
||||||
|
observer.onDiagnostic(
|
||||||
|
'CycleDetection disabled',
|
||||||
|
details: {
|
||||||
|
'event': 'disable',
|
||||||
|
'description': 'cycle detection disabled',
|
||||||
|
},
|
||||||
|
);
|
||||||
_cycleDetector = null;
|
_cycleDetector = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,7 +180,14 @@ mixin CycleDetectionMixin {
|
|||||||
final cycleStartIndex = _cycleDetector!._resolutionHistory.indexOf(dependencyKey);
|
final cycleStartIndex = _cycleDetector!._resolutionHistory.indexOf(dependencyKey);
|
||||||
final cycle = _cycleDetector!._resolutionHistory.sublist(cycleStartIndex)
|
final cycle = _cycleDetector!._resolutionHistory.sublist(cycleStartIndex)
|
||||||
..add(dependencyKey);
|
..add(dependencyKey);
|
||||||
|
observer.onCycleDetected(
|
||||||
|
cycle,
|
||||||
|
);
|
||||||
|
observer.onError(
|
||||||
|
'Cycle detected for $dependencyKey',
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
);
|
||||||
throw CircularDependencyException(
|
throw CircularDependencyException(
|
||||||
'Circular dependency detected for $dependencyKey',
|
'Circular dependency detected for $dependencyKey',
|
||||||
cycle,
|
cycle,
|
||||||
|
|||||||
63
cherrypick/lib/src/disposable.dart
Normal file
63
cherrypick/lib/src/disposable.dart
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
//
|
||||||
|
// 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
|
||||||
|
// https://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:async';
|
||||||
|
|
||||||
|
/// An interface for resources that require explicit cleanup, used by the CherryPick dependency injection container.
|
||||||
|
///
|
||||||
|
/// If an object implements [Disposable], the CherryPick DI container will automatically call [dispose]
|
||||||
|
/// when the corresponding scope is cleaned up. Use [Disposable] for closing streams, files, controllers, services, etc.
|
||||||
|
/// Both synchronous and asynchronous cleanup is supported:
|
||||||
|
/// - For sync disposables, implement [dispose] as a `void` or simply return nothing.
|
||||||
|
/// - For async disposables, implement [dispose] returning a [Future].
|
||||||
|
///
|
||||||
|
/// Example: Synchronous Disposable
|
||||||
|
/// ```dart
|
||||||
|
/// class MyLogger implements Disposable {
|
||||||
|
/// void dispose() {
|
||||||
|
/// print('Logger closed!');
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Example: Asynchronous Disposable
|
||||||
|
/// ```dart
|
||||||
|
/// class MyConnection implements Disposable {
|
||||||
|
/// @override
|
||||||
|
/// Future<void> dispose() async {
|
||||||
|
/// await connection.close();
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Usage with CherryPick DI Module
|
||||||
|
/// ```dart
|
||||||
|
/// final scope = openRootScope();
|
||||||
|
/// scope.installModules([
|
||||||
|
/// Module((b) {
|
||||||
|
/// b.bind<MyLogger>((_) => MyLogger());
|
||||||
|
/// b.bindAsync<MyConnection>((_) async => MyConnection());
|
||||||
|
/// }),
|
||||||
|
/// ]);
|
||||||
|
/// // ...
|
||||||
|
/// await scope.close(); // will automatically call dispose on all Disposables
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// This pattern ensures that all resources are released safely and automatically when the scope is destroyed.
|
||||||
|
abstract class Disposable {
|
||||||
|
/// Releases all resources held by this object.
|
||||||
|
///
|
||||||
|
/// Implement cleanup logic (closing streams, sockets, files, etc.) within this method.
|
||||||
|
/// Return a [Future] for async cleanup, or nothing (`void`) for synchronous cleanup.
|
||||||
|
FutureOr<void> dispose();
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
@@ -12,12 +12,15 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
import 'package:cherrypick/src/cycle_detector.dart';
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
|
||||||
|
|
||||||
/// RU: Глобальный детектор циклических зависимостей для всей иерархии скоупов.
|
/// RU: Глобальный детектор циклических зависимостей для всей иерархии скоупов.
|
||||||
/// ENG: Global circular dependency detector for entire scope hierarchy.
|
/// ENG: Global circular dependency detector for entire scope hierarchy.
|
||||||
class GlobalCycleDetector {
|
class GlobalCycleDetector {
|
||||||
static GlobalCycleDetector? _instance;
|
static GlobalCycleDetector? _instance;
|
||||||
|
|
||||||
|
final CherryPickObserver _observer;
|
||||||
|
|
||||||
// Глобальный стек разрешения зависимостей
|
// Глобальный стек разрешения зависимостей
|
||||||
final Set<String> _globalResolutionStack = HashSet<String>();
|
final Set<String> _globalResolutionStack = HashSet<String>();
|
||||||
@@ -28,12 +31,12 @@ class GlobalCycleDetector {
|
|||||||
// Карта активных детекторов по скоупам
|
// Карта активных детекторов по скоупам
|
||||||
final Map<String, CycleDetector> _scopeDetectors = HashMap<String, CycleDetector>();
|
final Map<String, CycleDetector> _scopeDetectors = HashMap<String, CycleDetector>();
|
||||||
|
|
||||||
GlobalCycleDetector._internal();
|
GlobalCycleDetector._internal({required CherryPickObserver observer}): _observer = observer;
|
||||||
|
|
||||||
/// RU: Получить единственный экземпляр глобального детектора.
|
/// RU: Получить единственный экземпляр глобального детектора.
|
||||||
/// ENG: Get singleton instance of global detector.
|
/// ENG: Get singleton instance of global detector.
|
||||||
static GlobalCycleDetector get instance {
|
static GlobalCycleDetector get instance {
|
||||||
_instance ??= GlobalCycleDetector._internal();
|
_instance ??= GlobalCycleDetector._internal(observer: CherryPick.globalObserver);
|
||||||
return _instance!;
|
return _instance!;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +58,15 @@ class GlobalCycleDetector {
|
|||||||
// Найдена глобальная циклическая зависимость
|
// Найдена глобальная циклическая зависимость
|
||||||
final cycleStartIndex = _globalResolutionHistory.indexOf(dependencyKey);
|
final cycleStartIndex = _globalResolutionHistory.indexOf(dependencyKey);
|
||||||
final cycle = _globalResolutionHistory.sublist(cycleStartIndex)..add(dependencyKey);
|
final cycle = _globalResolutionHistory.sublist(cycleStartIndex)..add(dependencyKey);
|
||||||
|
_observer.onCycleDetected(
|
||||||
|
cycle,
|
||||||
|
scopeName: scopeId,
|
||||||
|
);
|
||||||
|
_observer.onError(
|
||||||
|
'Global circular dependency detected for $dependencyKey',
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
);
|
||||||
throw CircularDependencyException(
|
throw CircularDependencyException(
|
||||||
'Global circular dependency detected for $dependencyKey',
|
'Global circular dependency detected for $dependencyKey',
|
||||||
cycle,
|
cycle,
|
||||||
@@ -93,7 +104,15 @@ class GlobalCycleDetector {
|
|||||||
final cycleStartIndex = _globalResolutionHistory.indexOf(dependencyKey);
|
final cycleStartIndex = _globalResolutionHistory.indexOf(dependencyKey);
|
||||||
final cycle = _globalResolutionHistory.sublist(cycleStartIndex)
|
final cycle = _globalResolutionHistory.sublist(cycleStartIndex)
|
||||||
..add(dependencyKey);
|
..add(dependencyKey);
|
||||||
|
_observer.onCycleDetected(
|
||||||
|
cycle,
|
||||||
|
scopeName: scopeId,
|
||||||
|
);
|
||||||
|
_observer.onError(
|
||||||
|
'Global circular dependency detected for $dependencyKey',
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
);
|
||||||
throw CircularDependencyException(
|
throw CircularDependencyException(
|
||||||
'Global circular dependency detected for $dependencyKey',
|
'Global circular dependency detected for $dependencyKey',
|
||||||
cycle,
|
cycle,
|
||||||
@@ -117,7 +136,7 @@ class GlobalCycleDetector {
|
|||||||
/// RU: Получить детектор для конкретного скоупа.
|
/// RU: Получить детектор для конкретного скоупа.
|
||||||
/// ENG: Get detector for specific scope.
|
/// ENG: Get detector for specific scope.
|
||||||
CycleDetector getScopeDetector(String scopeId) {
|
CycleDetector getScopeDetector(String scopeId) {
|
||||||
return _scopeDetectors.putIfAbsent(scopeId, () => CycleDetector());
|
return _scopeDetectors.putIfAbsent(scopeId, () => CycleDetector(observer: CherryPick.globalObserver));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Удалить детектор для скоупа.
|
/// RU: Удалить детектор для скоупа.
|
||||||
|
|||||||
@@ -3,77 +3,120 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import 'package:cherrypick/src/scope.dart';
|
import 'package:cherrypick/src/scope.dart';
|
||||||
import 'package:cherrypick/src/global_cycle_detector.dart';
|
import 'package:cherrypick/src/global_cycle_detector.dart';
|
||||||
|
import 'package:cherrypick/src/observer.dart';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
|
|
||||||
Scope? _rootScope;
|
Scope? _rootScope;
|
||||||
|
|
||||||
|
/// Global logger for all [Scope]s managed by [CherryPick].
|
||||||
|
///
|
||||||
|
/// Defaults to [SilentLogger] unless set via [setGlobalLogger].
|
||||||
|
CherryPickObserver _globalObserver = SilentCherryPickObserver();
|
||||||
|
|
||||||
|
/// Whether global local-cycle detection is enabled for all Scopes ([Scope.enableCycleDetection]).
|
||||||
bool _globalCycleDetectionEnabled = false;
|
bool _globalCycleDetectionEnabled = false;
|
||||||
|
|
||||||
|
/// Whether global cross-scope cycle detection is enabled ([Scope.enableGlobalCycleDetection]).
|
||||||
bool _globalCrossScopeCycleDetectionEnabled = false;
|
bool _globalCrossScopeCycleDetectionEnabled = false;
|
||||||
|
|
||||||
|
/// Static facade for managing dependency graph, root scope, subscopes, logger, and global settings in the CherryPick DI container.
|
||||||
|
///
|
||||||
|
/// - Provides a singleton root scope for simple integration.
|
||||||
|
/// - Supports hierarchical/named subscopes by string path.
|
||||||
|
/// - Manages global/protected logging and DI diagnostics.
|
||||||
|
/// - Suitable for most application & CLI scenarios. For test isolation, manually create [Scope]s instead.
|
||||||
|
///
|
||||||
|
/// ### Example: Opening a root scope and installing modules
|
||||||
|
/// ```dart
|
||||||
|
/// class AppModule extends Module {
|
||||||
|
/// @override
|
||||||
|
/// void builder(Scope scope) {
|
||||||
|
/// scope.bind<Service>().toProvide(() => ServiceImpl());
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// final root = CherryPick.openRootScope();
|
||||||
|
/// root.installModules([AppModule()]);
|
||||||
|
/// final service = root.resolve<Service>();
|
||||||
|
/// ```
|
||||||
class CherryPick {
|
class CherryPick {
|
||||||
/// RU: Метод открывает главный [Scope].
|
/// Sets the global logger for all [Scope]s created by CherryPick.
|
||||||
/// ENG: The method opens the main [Scope].
|
|
||||||
///
|
///
|
||||||
/// return
|
/// Allows customizing log output and DI diagnostics globally.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// CherryPick.setGlobalLogger(DefaultLogger());
|
||||||
|
/// ```
|
||||||
|
static void setGlobalObserver(CherryPickObserver observer) {
|
||||||
|
_globalObserver = observer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the current global logger used by CherryPick.
|
||||||
|
static CherryPickObserver get globalObserver => _globalObserver;
|
||||||
|
|
||||||
|
/// Returns the singleton root [Scope], creating it if needed.
|
||||||
|
///
|
||||||
|
/// Applies configured [globalLogger] and cycle detection settings.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// final root = CherryPick.openRootScope();
|
||||||
|
/// ```
|
||||||
static Scope openRootScope() {
|
static Scope openRootScope() {
|
||||||
_rootScope ??= Scope(null);
|
_rootScope ??= Scope(null, observer: _globalObserver);
|
||||||
|
// Apply cycle detection settings
|
||||||
// Применяем глобальную настройку обнаружения циклических зависимостей
|
|
||||||
if (_globalCycleDetectionEnabled && !_rootScope!.isCycleDetectionEnabled) {
|
if (_globalCycleDetectionEnabled && !_rootScope!.isCycleDetectionEnabled) {
|
||||||
_rootScope!.enableCycleDetection();
|
_rootScope!.enableCycleDetection();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Применяем глобальную настройку обнаружения между скоупами
|
|
||||||
if (_globalCrossScopeCycleDetectionEnabled && !_rootScope!.isGlobalCycleDetectionEnabled) {
|
if (_globalCrossScopeCycleDetectionEnabled && !_rootScope!.isGlobalCycleDetectionEnabled) {
|
||||||
_rootScope!.enableGlobalCycleDetection();
|
_rootScope!.enableGlobalCycleDetection();
|
||||||
}
|
}
|
||||||
|
|
||||||
return _rootScope!;
|
return _rootScope!;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Метод закрывает главный [Scope].
|
/// Disposes and resets the root [Scope] singleton.
|
||||||
/// ENG: The method close the main [Scope].
|
|
||||||
///
|
///
|
||||||
|
/// Call before tests or when needing full re-initialization.
|
||||||
///
|
///
|
||||||
static void closeRootScope() {
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// CherryPick.closeRootScope();
|
||||||
|
/// ```
|
||||||
|
static Future<void> closeRootScope() async {
|
||||||
if (_rootScope != null) {
|
if (_rootScope != null) {
|
||||||
|
await _rootScope!.dispose(); // Автоматический вызов dispose для rootScope!
|
||||||
_rootScope = null;
|
_rootScope = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Глобально включает обнаружение циклических зависимостей для всех новых скоупов.
|
/// Globally enables cycle detection for all new [Scope]s created by CherryPick.
|
||||||
/// ENG: Globally enables circular dependency detection for all new scopes.
|
|
||||||
///
|
///
|
||||||
/// Этот метод влияет на все скоупы, создаваемые через CherryPick.
|
/// Strongly recommended for safety in all projects.
|
||||||
/// This method affects all scopes created through CherryPick.
|
|
||||||
///
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
/// ```dart
|
/// ```dart
|
||||||
/// CherryPick.enableGlobalCycleDetection();
|
/// CherryPick.enableGlobalCycleDetection();
|
||||||
/// final scope = CherryPick.openRootScope(); // Автоматически включено обнаружение
|
|
||||||
/// ```
|
/// ```
|
||||||
static void enableGlobalCycleDetection() {
|
static void enableGlobalCycleDetection() {
|
||||||
_globalCycleDetectionEnabled = true;
|
_globalCycleDetectionEnabled = true;
|
||||||
|
|
||||||
// Включаем для уже существующего root scope, если он есть
|
|
||||||
if (_rootScope != null) {
|
if (_rootScope != null) {
|
||||||
_rootScope!.enableCycleDetection();
|
_rootScope!.enableCycleDetection();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Глобально отключает обнаружение циклических зависимостей.
|
/// Disables global local cycle detection. Existing and new scopes won't check for local cycles.
|
||||||
/// ENG: Globally disables circular dependency detection.
|
|
||||||
///
|
|
||||||
/// Рекомендуется использовать в production для максимальной производительности.
|
|
||||||
/// Recommended for production use for maximum performance.
|
|
||||||
///
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
/// ```dart
|
/// ```dart
|
||||||
@@ -81,85 +124,63 @@ class CherryPick {
|
|||||||
/// ```
|
/// ```
|
||||||
static void disableGlobalCycleDetection() {
|
static void disableGlobalCycleDetection() {
|
||||||
_globalCycleDetectionEnabled = false;
|
_globalCycleDetectionEnabled = false;
|
||||||
|
|
||||||
// Отключаем для уже существующего root scope, если он есть
|
|
||||||
if (_rootScope != null) {
|
if (_rootScope != null) {
|
||||||
_rootScope!.disableCycleDetection();
|
_rootScope!.disableCycleDetection();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Проверяет, включено ли глобальное обнаружение циклических зависимостей.
|
/// Returns `true` if global local cycle detection is enabled.
|
||||||
/// ENG: Checks if global circular dependency detection is enabled.
|
|
||||||
///
|
|
||||||
/// return true если включено, false если отключено
|
|
||||||
/// return true if enabled, false if disabled
|
|
||||||
static bool get isGlobalCycleDetectionEnabled => _globalCycleDetectionEnabled;
|
static bool get isGlobalCycleDetectionEnabled => _globalCycleDetectionEnabled;
|
||||||
|
|
||||||
/// RU: Включает обнаружение циклических зависимостей для конкретного скоупа.
|
/// Enables cycle detection for a particular scope tree.
|
||||||
/// ENG: Enables circular dependency detection for a specific scope.
|
|
||||||
///
|
///
|
||||||
/// [scopeName] - имя скоупа (пустая строка для root scope)
|
/// [scopeName] - hierarchical string path (e.g. 'feature.api'), or empty for root.
|
||||||
/// [scopeName] - scope name (empty string for root scope)
|
/// [separator] - path separator (default: '.'), e.g. '/' for "feature/api/module"
|
||||||
///
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
/// ```dart
|
/// ```dart
|
||||||
/// CherryPick.enableCycleDetectionForScope(); // Для root scope
|
/// CherryPick.enableCycleDetectionForScope(scopeName: 'api.feature');
|
||||||
/// CherryPick.enableCycleDetectionForScope(scopeName: 'feature.auth'); // Для конкретного scope
|
|
||||||
/// ```
|
/// ```
|
||||||
static void enableCycleDetectionForScope({String scopeName = '', String separator = '.'}) {
|
static void enableCycleDetectionForScope({String scopeName = '', String separator = '.'}) {
|
||||||
final scope = _getScope(scopeName, separator);
|
final scope = _getScope(scopeName, separator);
|
||||||
scope.enableCycleDetection();
|
scope.enableCycleDetection();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Отключает обнаружение циклических зависимостей для конкретного скоупа.
|
/// Disables cycle detection for a given scope. See [enableCycleDetectionForScope].
|
||||||
/// ENG: Disables circular dependency detection for a specific scope.
|
|
||||||
///
|
|
||||||
/// [scopeName] - имя скоупа (пустая строка для root scope)
|
|
||||||
/// [scopeName] - scope name (empty string for root scope)
|
|
||||||
static void disableCycleDetectionForScope({String scopeName = '', String separator = '.'}) {
|
static void disableCycleDetectionForScope({String scopeName = '', String separator = '.'}) {
|
||||||
final scope = _getScope(scopeName, separator);
|
final scope = _getScope(scopeName, separator);
|
||||||
scope.disableCycleDetection();
|
scope.disableCycleDetection();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Проверяет, включено ли обнаружение циклических зависимостей для конкретного скоупа.
|
/// Returns `true` if cycle detection is enabled for the requested scope.
|
||||||
/// ENG: Checks if circular dependency detection is enabled for a specific scope.
|
|
||||||
///
|
///
|
||||||
/// [scopeName] - имя скоупа (пустая строка для root scope)
|
/// Example:
|
||||||
/// [scopeName] - scope name (empty string for root scope)
|
/// ```dart
|
||||||
///
|
/// CherryPick.isCycleDetectionEnabledForScope(scopeName: 'feature.api');
|
||||||
/// return true если включено, false если отключено
|
/// ```
|
||||||
/// return true if enabled, false if disabled
|
|
||||||
static bool isCycleDetectionEnabledForScope({String scopeName = '', String separator = '.'}) {
|
static bool isCycleDetectionEnabledForScope({String scopeName = '', String separator = '.'}) {
|
||||||
final scope = _getScope(scopeName, separator);
|
final scope = _getScope(scopeName, separator);
|
||||||
return scope.isCycleDetectionEnabled;
|
return scope.isCycleDetectionEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Возвращает текущую цепочку разрешения зависимостей для конкретного скоупа.
|
/// Returns the current dependency resolution chain inside the given scope.
|
||||||
/// ENG: Returns current dependency resolution chain for a specific scope.
|
|
||||||
///
|
///
|
||||||
/// Полезно для отладки и анализа зависимостей.
|
/// Useful for diagnostics (to print what types are currently resolving).
|
||||||
/// Useful for debugging and dependency analysis.
|
|
||||||
///
|
///
|
||||||
/// [scopeName] - имя скоупа (пустая строка для root scope)
|
/// Example:
|
||||||
/// [scopeName] - scope name (empty string for root scope)
|
/// ```dart
|
||||||
///
|
/// print(CherryPick.getCurrentResolutionChain(scopeName: 'feature.api'));
|
||||||
/// return список имен зависимостей в текущей цепочке разрешения
|
/// ```
|
||||||
/// return list of dependency names in current resolution chain
|
|
||||||
static List<String> getCurrentResolutionChain({String scopeName = '', String separator = '.'}) {
|
static List<String> getCurrentResolutionChain({String scopeName = '', String separator = '.'}) {
|
||||||
final scope = _getScope(scopeName, separator);
|
final scope = _getScope(scopeName, separator);
|
||||||
return scope.currentResolutionChain;
|
return scope.currentResolutionChain;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Создает новый скоуп с автоматически включенным обнаружением циклических зависимостей.
|
/// Opens the root scope and enables local cycle detection.
|
||||||
/// ENG: Creates a new scope with automatically enabled circular dependency detection.
|
|
||||||
///
|
|
||||||
/// Удобный метод для создания безопасных скоупов в development режиме.
|
|
||||||
/// Convenient method for creating safe scopes in development mode.
|
|
||||||
///
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
/// ```dart
|
/// ```dart
|
||||||
/// final scope = CherryPick.openSafeRootScope();
|
/// final safeRoot = CherryPick.openSafeRootScope();
|
||||||
/// // Обнаружение циклических зависимостей автоматически включено
|
|
||||||
/// ```
|
/// ```
|
||||||
static Scope openSafeRootScope() {
|
static Scope openSafeRootScope() {
|
||||||
final scope = openRootScope();
|
final scope = openRootScope();
|
||||||
@@ -167,16 +188,11 @@ class CherryPick {
|
|||||||
return scope;
|
return scope;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Создает новый дочерний скоуп с автоматически включенным обнаружением циклических зависимостей.
|
/// Opens a named/nested scope and enables local cycle detection for it.
|
||||||
/// ENG: Creates a new child scope with automatically enabled circular dependency detection.
|
|
||||||
///
|
|
||||||
/// [scopeName] - имя скоупа
|
|
||||||
/// [scopeName] - scope name
|
|
||||||
///
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
/// ```dart
|
/// ```dart
|
||||||
/// final scope = CherryPick.openSafeScope(scopeName: 'feature.auth');
|
/// final api = CherryPick.openSafeScope(scopeName: 'feature.api');
|
||||||
/// // Обнаружение циклических зависимостей автоматически включено
|
|
||||||
/// ```
|
/// ```
|
||||||
static Scope openSafeScope({String scopeName = '', String separator = '.'}) {
|
static Scope openSafeScope({String scopeName = '', String separator = '.'}) {
|
||||||
final scope = openScope(scopeName: scopeName, separator: separator);
|
final scope = openScope(scopeName: scopeName, separator: separator);
|
||||||
@@ -184,8 +200,8 @@ class CherryPick {
|
|||||||
return scope;
|
return scope;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Внутренний метод для получения скоупа по имени.
|
/// Returns a [Scope] by path (or the root if none specified).
|
||||||
/// ENG: Internal method to get scope by name.
|
/// Used for internal diagnostics & helpers.
|
||||||
static Scope _getScope(String scopeName, String separator) {
|
static Scope _getScope(String scopeName, String separator) {
|
||||||
if (scopeName.isEmpty) {
|
if (scopeName.isEmpty) {
|
||||||
return openRootScope();
|
return openRootScope();
|
||||||
@@ -193,91 +209,76 @@ class CherryPick {
|
|||||||
return openScope(scopeName: scopeName, separator: separator);
|
return openScope(scopeName: scopeName, separator: separator);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Метод открывает дочерний [Scope].
|
/// Opens (and creates nested subscopes if needed) a scope by hierarchical path.
|
||||||
/// ENG: The method open the child [Scope].
|
|
||||||
///
|
///
|
||||||
/// Дочерний [Scope] открывается с [scopeName]
|
/// [scopeName] - dot-separated path ("api.feature"). Empty = root.
|
||||||
/// Child [Scope] open with [scopeName]
|
/// [separator] - path delimiter (default: '.')
|
||||||
|
///
|
||||||
|
/// Applies global cycle detection settings to the returned scope.
|
||||||
///
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// final apiScope = CherryPick.openScope(scopeName: 'network.super.api');
|
||||||
/// ```
|
/// ```
|
||||||
/// final String scopeName = 'firstScope.secondScope';
|
|
||||||
/// final subScope = CherryPick.openScope(scopeName);
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
///
|
|
||||||
@experimental
|
@experimental
|
||||||
static Scope openScope({String scopeName = '', String separator = '.'}) {
|
static Scope openScope({String scopeName = '', String separator = '.'}) {
|
||||||
if (scopeName.isEmpty) {
|
if (scopeName.isEmpty) {
|
||||||
return openRootScope();
|
return openRootScope();
|
||||||
}
|
}
|
||||||
|
|
||||||
final nameParts = scopeName.split(separator);
|
final nameParts = scopeName.split(separator);
|
||||||
if (nameParts.isEmpty) {
|
if (nameParts.isEmpty) {
|
||||||
throw Exception('Can not open sub scope because scopeName can not split');
|
throw Exception('Can not open sub scope because scopeName can not split');
|
||||||
}
|
}
|
||||||
|
|
||||||
final scope = nameParts.fold(
|
final scope = nameParts.fold(
|
||||||
openRootScope(),
|
openRootScope(),
|
||||||
(Scope previousValue, String element) =>
|
(Scope previous, String element) => previous.openSubScope(element)
|
||||||
previousValue.openSubScope(element));
|
);
|
||||||
|
|
||||||
// Применяем глобальную настройку обнаружения циклических зависимостей
|
|
||||||
if (_globalCycleDetectionEnabled && !scope.isCycleDetectionEnabled) {
|
if (_globalCycleDetectionEnabled && !scope.isCycleDetectionEnabled) {
|
||||||
scope.enableCycleDetection();
|
scope.enableCycleDetection();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Применяем глобальную настройку обнаружения между скоупами
|
|
||||||
if (_globalCrossScopeCycleDetectionEnabled && !scope.isGlobalCycleDetectionEnabled) {
|
if (_globalCrossScopeCycleDetectionEnabled && !scope.isGlobalCycleDetectionEnabled) {
|
||||||
scope.enableGlobalCycleDetection();
|
scope.enableGlobalCycleDetection();
|
||||||
}
|
}
|
||||||
|
|
||||||
return scope;
|
return scope;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Метод открывает дочерний [Scope].
|
/// Closes a named or root scope (if [scopeName] is omitted).
|
||||||
/// ENG: The method open the child [Scope].
|
|
||||||
///
|
///
|
||||||
/// Дочерний [Scope] открывается с [scopeName]
|
/// [scopeName] - dot-separated hierarchical path (e.g. 'api.feature'). Empty = root.
|
||||||
/// Child [Scope] open with [scopeName]
|
/// [separator] - path delimiter.
|
||||||
///
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// CherryPick.closeScope(scopeName: 'network.super.api');
|
||||||
/// ```
|
/// ```
|
||||||
/// final String scopeName = 'firstScope.secondScope';
|
|
||||||
/// final subScope = CherryPick.closeScope(scopeName);
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
///
|
|
||||||
@experimental
|
@experimental
|
||||||
static void closeScope({String scopeName = '', String separator = '.'}) {
|
static Future<void> closeScope({String scopeName = '', String separator = '.'}) async {
|
||||||
if (scopeName.isEmpty) {
|
if (scopeName.isEmpty) {
|
||||||
closeRootScope();
|
await closeRootScope();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final nameParts = scopeName.split(separator);
|
final nameParts = scopeName.split(separator);
|
||||||
if (nameParts.isEmpty) {
|
if (nameParts.isEmpty) {
|
||||||
throw Exception(
|
throw Exception('Can not close sub scope because scopeName can not split');
|
||||||
'Can not close sub scope because scopeName can not split');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nameParts.length > 1) {
|
if (nameParts.length > 1) {
|
||||||
final lastPart = nameParts.removeLast();
|
final lastPart = nameParts.removeLast();
|
||||||
|
|
||||||
final scope = nameParts.fold(
|
final scope = nameParts.fold(
|
||||||
openRootScope(),
|
openRootScope(),
|
||||||
(Scope previousValue, String element) =>
|
(Scope previous, String element) => previous.openSubScope(element)
|
||||||
previousValue.openSubScope(element));
|
);
|
||||||
scope.closeSubScope(lastPart);
|
await scope.closeSubScope(lastPart);
|
||||||
} else {
|
} else {
|
||||||
openRootScope().closeSubScope(nameParts[0]);
|
await openRootScope().closeSubScope(nameParts.first);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Глобально включает обнаружение циклических зависимостей между скоупами.
|
/// Enables cross-scope cycle detection globally.
|
||||||
/// ENG: Globally enables cross-scope circular dependency detection.
|
|
||||||
///
|
///
|
||||||
/// Этот режим обнаруживает циклические зависимости во всей иерархии скоупов.
|
/// This will activate detection of cycles that may span across multiple scopes
|
||||||
/// This mode detects circular dependencies across the entire scope hierarchy.
|
/// in the entire dependency graph. All new and existing [Scope]s will participate.
|
||||||
|
///
|
||||||
|
/// Strongly recommended for complex solutions with modular architecture.
|
||||||
///
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
/// ```dart
|
/// ```dart
|
||||||
@@ -285,15 +286,15 @@ class CherryPick {
|
|||||||
/// ```
|
/// ```
|
||||||
static void enableGlobalCrossScopeCycleDetection() {
|
static void enableGlobalCrossScopeCycleDetection() {
|
||||||
_globalCrossScopeCycleDetectionEnabled = true;
|
_globalCrossScopeCycleDetectionEnabled = true;
|
||||||
|
|
||||||
// Включаем для уже существующего root scope, если он есть
|
|
||||||
if (_rootScope != null) {
|
if (_rootScope != null) {
|
||||||
_rootScope!.enableGlobalCycleDetection();
|
_rootScope!.enableGlobalCycleDetection();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Глобально отключает обнаружение циклических зависимостей между скоупами.
|
/// Disables global cross-scope cycle detection.
|
||||||
/// ENG: Globally disables cross-scope circular dependency detection.
|
///
|
||||||
|
/// Existing and new scopes stop checking for global (cross-scope) cycles.
|
||||||
|
/// The internal global cycle detector will be cleared as well.
|
||||||
///
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
/// ```dart
|
/// ```dart
|
||||||
@@ -301,54 +302,55 @@ class CherryPick {
|
|||||||
/// ```
|
/// ```
|
||||||
static void disableGlobalCrossScopeCycleDetection() {
|
static void disableGlobalCrossScopeCycleDetection() {
|
||||||
_globalCrossScopeCycleDetectionEnabled = false;
|
_globalCrossScopeCycleDetectionEnabled = false;
|
||||||
|
|
||||||
// Отключаем для уже существующего root scope, если он есть
|
|
||||||
if (_rootScope != null) {
|
if (_rootScope != null) {
|
||||||
_rootScope!.disableGlobalCycleDetection();
|
_rootScope!.disableGlobalCycleDetection();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Очищаем глобальный детектор
|
|
||||||
GlobalCycleDetector.instance.clear();
|
GlobalCycleDetector.instance.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Проверяет, включено ли глобальное обнаружение циклических зависимостей между скоупами.
|
/// Returns `true` if global cross-scope cycle detection is enabled.
|
||||||
/// ENG: Checks if global cross-scope circular dependency detection is enabled.
|
|
||||||
///
|
///
|
||||||
/// return true если включено, false если отключено
|
/// Example:
|
||||||
/// return true if enabled, false if disabled
|
/// ```dart
|
||||||
|
/// if (CherryPick.isGlobalCrossScopeCycleDetectionEnabled) {
|
||||||
|
/// print('Global cross-scope detection is ON');
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
static bool get isGlobalCrossScopeCycleDetectionEnabled => _globalCrossScopeCycleDetectionEnabled;
|
static bool get isGlobalCrossScopeCycleDetectionEnabled => _globalCrossScopeCycleDetectionEnabled;
|
||||||
|
|
||||||
/// RU: Возвращает глобальную цепочку разрешения зависимостей.
|
/// Returns the current global dependency resolution chain (across all scopes).
|
||||||
/// ENG: Returns global dependency resolution chain.
|
|
||||||
///
|
///
|
||||||
/// Полезно для отладки циклических зависимостей между скоупами.
|
/// Shows the cross-scope resolution stack, which is useful for advanced diagnostics
|
||||||
/// Useful for debugging circular dependencies across scopes.
|
/// and debugging cycle issues that occur between scopes.
|
||||||
///
|
///
|
||||||
/// return список имен зависимостей в глобальной цепочке разрешения
|
/// Example:
|
||||||
/// return list of dependency names in global resolution chain
|
/// ```dart
|
||||||
|
/// print(CherryPick.getGlobalResolutionChain());
|
||||||
|
/// ```
|
||||||
static List<String> getGlobalResolutionChain() {
|
static List<String> getGlobalResolutionChain() {
|
||||||
return GlobalCycleDetector.instance.globalResolutionChain;
|
return GlobalCycleDetector.instance.globalResolutionChain;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Очищает все состояние глобального детектора циклических зависимостей.
|
/// Clears the global cross-scope cycle detector.
|
||||||
/// ENG: Clears all global circular dependency detector state.
|
|
||||||
///
|
///
|
||||||
/// Полезно для тестов и сброса состояния.
|
/// Useful in tests or when resetting application state.
|
||||||
/// Useful for tests and state reset.
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// CherryPick.clearGlobalCycleDetector();
|
||||||
|
/// ```
|
||||||
static void clearGlobalCycleDetector() {
|
static void clearGlobalCycleDetector() {
|
||||||
GlobalCycleDetector.reset();
|
GlobalCycleDetector.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Создает новый скоуп с автоматически включенным глобальным обнаружением циклических зависимостей.
|
/// Opens the root scope with both local and global cross-scope cycle detection enabled.
|
||||||
/// ENG: Creates a new scope with automatically enabled global circular dependency detection.
|
|
||||||
///
|
///
|
||||||
/// Этот скоуп будет отслеживать циклические зависимости во всей иерархии.
|
/// This is the safest way to start IoC for most apps — cycles will be detected
|
||||||
/// This scope will track circular dependencies across the entire hierarchy.
|
/// both inside a single scope and between scopes.
|
||||||
///
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
/// ```dart
|
/// ```dart
|
||||||
/// final scope = CherryPick.openGlobalSafeRootScope();
|
/// final root = CherryPick.openGlobalSafeRootScope();
|
||||||
/// // Глобальное обнаружение циклических зависимостей автоматически включено
|
|
||||||
/// ```
|
/// ```
|
||||||
static Scope openGlobalSafeRootScope() {
|
static Scope openGlobalSafeRootScope() {
|
||||||
final scope = openRootScope();
|
final scope = openRootScope();
|
||||||
@@ -357,16 +359,13 @@ class CherryPick {
|
|||||||
return scope;
|
return scope;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Создает новый дочерний скоуп с автоматически включенным глобальным обнаружением циклических зависимостей.
|
/// Opens the given named/nested scope and enables both local and cross-scope cycle detection on it.
|
||||||
/// ENG: Creates a new child scope with automatically enabled global circular dependency detection.
|
|
||||||
///
|
///
|
||||||
/// [scopeName] - имя скоупа
|
/// Recommended when creating feature/module scopes in large apps.
|
||||||
/// [scopeName] - scope name
|
|
||||||
///
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
/// ```dart
|
/// ```dart
|
||||||
/// final scope = CherryPick.openGlobalSafeScope(scopeName: 'feature.auth');
|
/// final featureScope = CherryPick.openGlobalSafeScope(scopeName: 'featureA.api');
|
||||||
/// // Глобальное обнаружение циклических зависимостей автоматически включено
|
|
||||||
/// ```
|
/// ```
|
||||||
static Scope openGlobalSafeScope({String scopeName = '', String separator = '.'}) {
|
static Scope openGlobalSafeScope({String scopeName = '', String separator = '.'}) {
|
||||||
final scope = openScope(scopeName: scopeName, separator: separator);
|
final scope = openScope(scopeName: scopeName, separator: separator);
|
||||||
@@ -374,4 +373,4 @@ class CherryPick {
|
|||||||
scope.enableGlobalCycleDetection();
|
scope.enableGlobalCycleDetection();
|
||||||
return scope;
|
return scope;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
236
cherrypick/lib/src/observer.dart
Normal file
236
cherrypick/lib/src/observer.dart
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
//
|
||||||
|
// 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
|
||||||
|
// https://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.
|
||||||
|
//
|
||||||
|
|
||||||
|
/// An abstract Observer for CherryPick DI container events.
|
||||||
|
///
|
||||||
|
/// Extend this class to react to and log various events inside the CherryPick Dependency Injection container.
|
||||||
|
/// Allows monitoring of registration, creation, disposal, module changes, cache hits/misses, cycles, and
|
||||||
|
/// errors/warnings for improved diagnostics and debugging.
|
||||||
|
///
|
||||||
|
/// All methods have detailed event information, including name, type, scope, and other arguments.
|
||||||
|
///
|
||||||
|
/// Example: Logging and debugging container events
|
||||||
|
/// ```dart
|
||||||
|
/// final CherryPickObserver observer = PrintCherryPickObserver();
|
||||||
|
/// // Pass observer to CherryPick during setup
|
||||||
|
/// CherryPick.openRootScope(observer: observer);
|
||||||
|
/// ```
|
||||||
|
abstract class CherryPickObserver {
|
||||||
|
// === Registration and instance lifecycle ===
|
||||||
|
/// Called when a binding is registered within the container (new dependency mapping).
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// observer.onBindingRegistered('MyService', MyService, scopeName: 'root');
|
||||||
|
/// ```
|
||||||
|
void onBindingRegistered(String name, Type type, {String? scopeName});
|
||||||
|
|
||||||
|
/// Called when an instance is requested (before it is created or retrieved from cache).
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// observer.onInstanceRequested('MyService', MyService, scopeName: 'root');
|
||||||
|
/// ```
|
||||||
|
void onInstanceRequested(String name, Type type, {String? scopeName});
|
||||||
|
|
||||||
|
/// Called when a new instance is successfully created.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// observer.onInstanceCreated('MyService', MyService, instance, scopeName: 'root');
|
||||||
|
/// ```
|
||||||
|
void onInstanceCreated(String name, Type type, Object instance, {String? scopeName});
|
||||||
|
|
||||||
|
/// Called when an instance is disposed (removed from cache and/or finalized).
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// observer.onInstanceDisposed('MyService', MyService, instance, scopeName: 'root');
|
||||||
|
/// ```
|
||||||
|
void onInstanceDisposed(String name, Type type, Object instance, {String? scopeName});
|
||||||
|
|
||||||
|
// === Module events ===
|
||||||
|
/// Called when modules are installed into the container.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// observer.onModulesInstalled(['NetworkModule', 'RepositoryModule'], scopeName: 'root');
|
||||||
|
/// ```
|
||||||
|
void onModulesInstalled(List<String> moduleNames, {String? scopeName});
|
||||||
|
|
||||||
|
/// Called when modules are removed from the container.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// observer.onModulesRemoved(['RepositoryModule'], scopeName: 'root');
|
||||||
|
/// ```
|
||||||
|
void onModulesRemoved(List<String> moduleNames, {String? scopeName});
|
||||||
|
|
||||||
|
// === Scope lifecycle ===
|
||||||
|
/// Called when a new DI scope is opened (for example, starting a new feature or screen).
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// observer.onScopeOpened('user-session');
|
||||||
|
/// ```
|
||||||
|
void onScopeOpened(String name);
|
||||||
|
|
||||||
|
/// Called when an existing DI scope is closed.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// observer.onScopeClosed('user-session');
|
||||||
|
/// ```
|
||||||
|
void onScopeClosed(String name);
|
||||||
|
|
||||||
|
// === Cycle detection ===
|
||||||
|
/// Called if a dependency cycle is detected during resolution.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// observer.onCycleDetected(['A', 'B', 'C', 'A'], scopeName: 'root');
|
||||||
|
/// ```
|
||||||
|
void onCycleDetected(List<String> chain, {String? scopeName});
|
||||||
|
|
||||||
|
// === Cache events ===
|
||||||
|
/// Called when an instance is found in the cache.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// observer.onCacheHit('MyService', MyService, scopeName: 'root');
|
||||||
|
/// ```
|
||||||
|
void onCacheHit(String name, Type type, {String? scopeName});
|
||||||
|
|
||||||
|
/// Called when an instance is not found in the cache and should be created.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// observer.onCacheMiss('MyService', MyService, scopeName: 'root');
|
||||||
|
/// ```
|
||||||
|
void onCacheMiss(String name, Type type, {String? scopeName});
|
||||||
|
|
||||||
|
// === Diagnostic ===
|
||||||
|
/// Used for custom diagnostic and debug messages.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// observer.onDiagnostic('Cache cleared', details: detailsObj);
|
||||||
|
/// ```
|
||||||
|
void onDiagnostic(String message, {Object? details});
|
||||||
|
|
||||||
|
// === Warnings & errors ===
|
||||||
|
/// Called on non-fatal, recoverable DI container warnings.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// observer.onWarning('Binding override', details: {...});
|
||||||
|
/// ```
|
||||||
|
void onWarning(String message, {Object? details});
|
||||||
|
|
||||||
|
/// Called on error (typically exceptions thrown during resolution, instantiation, or disposal).
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// observer.onError('Failed to resolve dependency', errorObj, stackTraceObj);
|
||||||
|
/// ```
|
||||||
|
void onError(String message, Object? error, StackTrace? stackTrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Diagnostic/Debug observer that prints all events
|
||||||
|
class PrintCherryPickObserver implements CherryPickObserver {
|
||||||
|
@override
|
||||||
|
void onBindingRegistered(String name, Type type, {String? scopeName}) =>
|
||||||
|
print('[binding][CherryPick] $name — $type (scope: $scopeName)');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInstanceRequested(String name, Type type, {String? scopeName}) =>
|
||||||
|
print('[request][CherryPick] $name — $type (scope: $scopeName)');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInstanceCreated(String name, Type type, Object instance, {String? scopeName}) =>
|
||||||
|
print('[create][CherryPick] $name — $type => $instance (scope: $scopeName)');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInstanceDisposed(String name, Type type, Object instance, {String? scopeName}) =>
|
||||||
|
print('[dispose][CherryPick] $name — $type => $instance (scope: $scopeName)');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onModulesInstalled(List<String> modules, {String? scopeName}) =>
|
||||||
|
print('[modules installed][CherryPick] ${modules.join(', ')} (scope: $scopeName)');
|
||||||
|
@override
|
||||||
|
void onModulesRemoved(List<String> modules, {String? scopeName}) =>
|
||||||
|
print('[modules removed][CherryPick] ${modules.join(', ')} (scope: $scopeName)');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onScopeOpened(String name) => print('[scope opened][CherryPick] $name');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onScopeClosed(String name) => print('[scope closed][CherryPick] $name');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onCycleDetected(List<String> chain, {String? scopeName}) =>
|
||||||
|
print('[cycle][CherryPick] Detected: ${chain.join(' -> ')}${scopeName != null ? ' (scope: $scopeName)' : ''}');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onCacheHit(String name, Type type, {String? scopeName}) =>
|
||||||
|
print('[cache hit][CherryPick] $name — $type (scope: $scopeName)');
|
||||||
|
@override
|
||||||
|
void onCacheMiss(String name, Type type, {String? scopeName}) =>
|
||||||
|
print('[cache miss][CherryPick] $name — $type (scope: $scopeName)');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onDiagnostic(String message, {Object? details}) =>
|
||||||
|
print('[diagnostic][CherryPick] $message ${details ?? ''}');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onWarning(String message, {Object? details}) =>
|
||||||
|
print('[warn][CherryPick] $message ${details ?? ''}');
|
||||||
|
@override
|
||||||
|
void onError(String message, Object? error, StackTrace? stackTrace) {
|
||||||
|
print('[error][CherryPick] $message');
|
||||||
|
if (error != null) print(' error: $error');
|
||||||
|
if (stackTrace != null) print(' stack: $stackTrace');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Silent observer: ignores all events
|
||||||
|
class SilentCherryPickObserver implements CherryPickObserver {
|
||||||
|
@override
|
||||||
|
void onBindingRegistered(String name, Type type, {String? scopeName}) {}
|
||||||
|
@override
|
||||||
|
void onInstanceRequested(String name, Type type, {String? scopeName}) {}
|
||||||
|
@override
|
||||||
|
void onInstanceCreated(String name, Type type, Object instance, {String? scopeName}) {}
|
||||||
|
@override
|
||||||
|
void onInstanceDisposed(String name, Type type, Object instance, {String? scopeName}) {}
|
||||||
|
@override
|
||||||
|
void onModulesInstalled(List<String> modules, {String? scopeName}) {}
|
||||||
|
@override
|
||||||
|
void onModulesRemoved(List<String> modules, {String? scopeName}) {}
|
||||||
|
@override
|
||||||
|
void onScopeOpened(String name) {}
|
||||||
|
@override
|
||||||
|
void onScopeClosed(String name) {}
|
||||||
|
@override
|
||||||
|
void onCycleDetected(List<String> chain, {String? scopeName}) {}
|
||||||
|
@override
|
||||||
|
void onCacheHit(String name, Type type, {String? scopeName}) {}
|
||||||
|
@override
|
||||||
|
void onCacheMiss(String name, Type type, {String? scopeName}) {}
|
||||||
|
@override
|
||||||
|
void onDiagnostic(String message, {Object? details}) {}
|
||||||
|
@override
|
||||||
|
void onWarning(String message, {Object? details}) {}
|
||||||
|
@override
|
||||||
|
void onError(String message, Object? error, StackTrace? stackTrace) {}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
@@ -14,15 +14,24 @@ import 'dart:collection';
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:cherrypick/src/cycle_detector.dart';
|
import 'package:cherrypick/src/cycle_detector.dart';
|
||||||
|
import 'package:cherrypick/src/disposable.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/binding_resolver.dart';
|
||||||
import 'package:cherrypick/src/module.dart';
|
import 'package:cherrypick/src/module.dart';
|
||||||
|
import 'package:cherrypick/src/observer.dart';
|
||||||
Scope openRootScope() => Scope(null);
|
// import 'package:cherrypick/src/log_format.dart';
|
||||||
|
|
||||||
class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
||||||
final Scope? _parentScope;
|
final Scope? _parentScope;
|
||||||
|
|
||||||
|
late final CherryPickObserver _observer;
|
||||||
|
|
||||||
|
@override
|
||||||
|
CherryPickObserver get observer => _observer;
|
||||||
|
|
||||||
|
/// COLLECTS all resolved instances that implement [Disposable].
|
||||||
|
final Set<Disposable> _disposables = HashSet();
|
||||||
|
|
||||||
/// RU: Метод возвращает родительский [Scope].
|
/// RU: Метод возвращает родительский [Scope].
|
||||||
///
|
///
|
||||||
/// ENG: The method returns the parent [Scope].
|
/// ENG: The method returns the parent [Scope].
|
||||||
@@ -32,9 +41,18 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
|
|
||||||
final Map<String, Scope> _scopeMap = HashMap();
|
final Map<String, Scope> _scopeMap = HashMap();
|
||||||
|
|
||||||
Scope(this._parentScope) {
|
Scope(this._parentScope, {required CherryPickObserver observer}) : _observer = observer {
|
||||||
// Генерируем уникальный ID для скоупа
|
|
||||||
setScopeId(_generateScopeId());
|
setScopeId(_generateScopeId());
|
||||||
|
observer.onScopeOpened(scopeId ?? 'NO_ID');
|
||||||
|
observer.onDiagnostic(
|
||||||
|
'Scope created: ${scopeId ?? 'NO_ID'}',
|
||||||
|
details: {
|
||||||
|
'type': 'Scope',
|
||||||
|
'name': scopeId ?? 'NO_ID',
|
||||||
|
if (_parentScope?.scopeId != null) 'parent': _parentScope!.scopeId,
|
||||||
|
'description': 'scope created',
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final Set<Module> _modulesList = HashSet();
|
final Set<Module> _modulesList = HashSet();
|
||||||
@@ -59,8 +77,8 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
/// return [Scope]
|
/// return [Scope]
|
||||||
Scope openSubScope(String name) {
|
Scope openSubScope(String name) {
|
||||||
if (!_scopeMap.containsKey(name)) {
|
if (!_scopeMap.containsKey(name)) {
|
||||||
final childScope = Scope(this);
|
final childScope = Scope(this, observer: observer); // Наследуем observer вниз по иерархии
|
||||||
|
// print removed (trace)
|
||||||
// Наследуем настройки обнаружения циклических зависимостей
|
// Наследуем настройки обнаружения циклических зависимостей
|
||||||
if (isCycleDetectionEnabled) {
|
if (isCycleDetectionEnabled) {
|
||||||
childScope.enableCycleDetection();
|
childScope.enableCycleDetection();
|
||||||
@@ -68,24 +86,45 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
if (isGlobalCycleDetectionEnabled) {
|
if (isGlobalCycleDetectionEnabled) {
|
||||||
childScope.enableGlobalCycleDetection();
|
childScope.enableGlobalCycleDetection();
|
||||||
}
|
}
|
||||||
|
|
||||||
_scopeMap[name] = childScope;
|
_scopeMap[name] = childScope;
|
||||||
|
observer.onDiagnostic(
|
||||||
|
'SubScope created: $name',
|
||||||
|
details: {
|
||||||
|
'type': 'SubScope',
|
||||||
|
'name': name,
|
||||||
|
'id': childScope.scopeId,
|
||||||
|
if (scopeId != null) 'parent': scopeId,
|
||||||
|
'description': 'subscope created',
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return _scopeMap[name]!;
|
return _scopeMap[name]!;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Метод закрывает дочерний (дополнительный) [Scope].
|
/// RU: Метод закрывает дочерний (дополнительный) [Scope] асинхронно.
|
||||||
///
|
///
|
||||||
/// ENG: The method closes child (additional) [Scope].
|
/// ENG: The method closes child (additional) [Scope] asynchronously.
|
||||||
///
|
///
|
||||||
/// return [Scope]
|
/// return [Future<void>]
|
||||||
void closeSubScope(String name) {
|
Future<void> closeSubScope(String name) async {
|
||||||
final childScope = _scopeMap[name];
|
final childScope = _scopeMap[name];
|
||||||
if (childScope != null) {
|
if (childScope != null) {
|
||||||
|
await childScope.dispose(); // асинхронный вызов
|
||||||
// Очищаем детектор для дочернего скоупа
|
// Очищаем детектор для дочернего скоупа
|
||||||
if (childScope.scopeId != null) {
|
if (childScope.scopeId != null) {
|
||||||
GlobalCycleDetector.instance.removeScopeDetector(childScope.scopeId!);
|
GlobalCycleDetector.instance.removeScopeDetector(childScope.scopeId!);
|
||||||
}
|
}
|
||||||
|
observer.onScopeClosed(childScope.scopeId ?? name);
|
||||||
|
observer.onDiagnostic(
|
||||||
|
'SubScope closed: $name',
|
||||||
|
details: {
|
||||||
|
'type': 'SubScope',
|
||||||
|
'name': name,
|
||||||
|
'id': childScope.scopeId,
|
||||||
|
if (scopeId != null) 'parent': scopeId,
|
||||||
|
'description': 'subscope closed',
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
_scopeMap.remove(name);
|
_scopeMap.remove(name);
|
||||||
}
|
}
|
||||||
@@ -97,8 +136,28 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
/// return [Scope]
|
/// return [Scope]
|
||||||
Scope installModules(List<Module> modules) {
|
Scope installModules(List<Module> modules) {
|
||||||
_modulesList.addAll(modules);
|
_modulesList.addAll(modules);
|
||||||
|
if (modules.isNotEmpty) {
|
||||||
|
observer.onModulesInstalled(
|
||||||
|
modules.map((m) => m.runtimeType.toString()).toList(),
|
||||||
|
scopeName: scopeId,
|
||||||
|
);
|
||||||
|
}
|
||||||
for (var module in modules) {
|
for (var module in modules) {
|
||||||
|
observer.onDiagnostic(
|
||||||
|
'Module installed: ${module.runtimeType}',
|
||||||
|
details: {
|
||||||
|
'type': 'Module',
|
||||||
|
'name': module.runtimeType.toString(),
|
||||||
|
'scope': scopeId,
|
||||||
|
'description': 'module installed',
|
||||||
|
},
|
||||||
|
);
|
||||||
module.builder(this);
|
module.builder(this);
|
||||||
|
// После builder: для всех новых биндингов
|
||||||
|
for (final binding in module.bindingSet) {
|
||||||
|
binding.observer = observer;
|
||||||
|
binding.logAllDeferred();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_rebuildResolversIndex();
|
_rebuildResolversIndex();
|
||||||
return this;
|
return this;
|
||||||
@@ -110,7 +169,20 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
///
|
///
|
||||||
/// return [Scope]
|
/// return [Scope]
|
||||||
Scope dropModules() {
|
Scope dropModules() {
|
||||||
// [AlexeyYuPopkov](https://github.com/AlexeyYuPopkov) Thank you for the [Removed exception "ConcurrentModificationError"](https://github.com/pese-git/cherrypick/pull/2)
|
if (_modulesList.isNotEmpty) {
|
||||||
|
observer.onModulesRemoved(
|
||||||
|
_modulesList.map((m) => m.runtimeType.toString()).toList(),
|
||||||
|
scopeName: scopeId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
observer.onDiagnostic(
|
||||||
|
'Modules dropped for scope: $scopeId',
|
||||||
|
details: {
|
||||||
|
'type': 'Scope',
|
||||||
|
'name': scopeId,
|
||||||
|
'description': 'modules dropped',
|
||||||
|
},
|
||||||
|
);
|
||||||
_modulesList.clear();
|
_modulesList.clear();
|
||||||
_rebuildResolversIndex();
|
_rebuildResolversIndex();
|
||||||
return this;
|
return this;
|
||||||
@@ -128,14 +200,36 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
/// return - returns an object of type [T] or [StateError]
|
/// return - returns an object of type [T] or [StateError]
|
||||||
///
|
///
|
||||||
T resolve<T>({String? named, dynamic params}) {
|
T resolve<T>({String? named, dynamic params}) {
|
||||||
|
observer.onInstanceRequested(T.toString(), T, scopeName: scopeId);
|
||||||
// Используем глобальное отслеживание, если включено
|
// Используем глобальное отслеживание, если включено
|
||||||
|
T result;
|
||||||
if (isGlobalCycleDetectionEnabled) {
|
if (isGlobalCycleDetectionEnabled) {
|
||||||
return withGlobalCycleDetection<T>(T, named, () {
|
try {
|
||||||
return _resolveWithLocalDetection<T>(named: named, params: params);
|
result = withGlobalCycleDetection<T>(T, named, () {
|
||||||
});
|
return _resolveWithLocalDetection<T>(named: named, params: params);
|
||||||
|
});
|
||||||
|
} catch (e, s) {
|
||||||
|
observer.onError(
|
||||||
|
'Global cycle detection failed during resolve: $T',
|
||||||
|
e,
|
||||||
|
s,
|
||||||
|
);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return _resolveWithLocalDetection<T>(named: named, params: params);
|
try {
|
||||||
|
result = _resolveWithLocalDetection<T>(named: named, params: params);
|
||||||
|
} catch (e, s) {
|
||||||
|
observer.onError(
|
||||||
|
'Failed to resolve: $T',
|
||||||
|
e,
|
||||||
|
s,
|
||||||
|
);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
_trackDisposable(result);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Разрешение с локальным детектором циклических зависимостей.
|
/// RU: Разрешение с локальным детектором циклических зависимостей.
|
||||||
@@ -144,8 +238,24 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
return withCycleDetection<T>(T, named, () {
|
return withCycleDetection<T>(T, named, () {
|
||||||
var resolved = _tryResolveInternal<T>(named: named, params: params);
|
var resolved = _tryResolveInternal<T>(named: named, params: params);
|
||||||
if (resolved != null) {
|
if (resolved != null) {
|
||||||
|
observer.onInstanceCreated(T.toString(), T, resolved, scopeName: scopeId);
|
||||||
|
observer.onDiagnostic(
|
||||||
|
'Successfully resolved: $T',
|
||||||
|
details: {
|
||||||
|
'type': 'Scope',
|
||||||
|
'name': scopeId,
|
||||||
|
'resolve': T.toString(),
|
||||||
|
if (named != null) 'named': named,
|
||||||
|
'description': 'successfully resolved',
|
||||||
|
},
|
||||||
|
);
|
||||||
return resolved;
|
return resolved;
|
||||||
} else {
|
} else {
|
||||||
|
observer.onError(
|
||||||
|
'Failed to resolve: $T',
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
);
|
||||||
throw StateError(
|
throw StateError(
|
||||||
'Can\'t resolve dependency `$T`. Maybe you forget register it?');
|
'Can\'t resolve dependency `$T`. Maybe you forget register it?');
|
||||||
}
|
}
|
||||||
@@ -157,13 +267,16 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
///
|
///
|
||||||
T? tryResolve<T>({String? named, dynamic params}) {
|
T? tryResolve<T>({String? named, dynamic params}) {
|
||||||
// Используем глобальное отслеживание, если включено
|
// Используем глобальное отслеживание, если включено
|
||||||
|
T? result;
|
||||||
if (isGlobalCycleDetectionEnabled) {
|
if (isGlobalCycleDetectionEnabled) {
|
||||||
return withGlobalCycleDetection<T?>(T, named, () {
|
result = withGlobalCycleDetection<T?>(T, named, () {
|
||||||
return _tryResolveWithLocalDetection<T>(named: named, params: params);
|
return _tryResolveWithLocalDetection<T>(named: named, params: params);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return _tryResolveWithLocalDetection<T>(named: named, params: params);
|
result = _tryResolveWithLocalDetection<T>(named: named, params: params);
|
||||||
}
|
}
|
||||||
|
if (result != null) _trackDisposable(result);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Попытка разрешения с локальным детектором циклических зависимостей.
|
/// RU: Попытка разрешения с локальным детектором циклических зависимостей.
|
||||||
@@ -201,13 +314,16 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
///
|
///
|
||||||
Future<T> resolveAsync<T>({String? named, dynamic params}) async {
|
Future<T> resolveAsync<T>({String? named, dynamic params}) async {
|
||||||
// Используем глобальное отслеживание, если включено
|
// Используем глобальное отслеживание, если включено
|
||||||
|
T result;
|
||||||
if (isGlobalCycleDetectionEnabled) {
|
if (isGlobalCycleDetectionEnabled) {
|
||||||
return withGlobalCycleDetection<Future<T>>(T, named, () async {
|
result = await withGlobalCycleDetection<Future<T>>(T, named, () async {
|
||||||
return await _resolveAsyncWithLocalDetection<T>(named: named, params: params);
|
return await _resolveAsyncWithLocalDetection<T>(named: named, params: params);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return await _resolveAsyncWithLocalDetection<T>(named: named, params: params);
|
result = await _resolveAsyncWithLocalDetection<T>(named: named, params: params);
|
||||||
}
|
}
|
||||||
|
_trackDisposable(result);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Асинхронное разрешение с локальным детектором циклических зависимостей.
|
/// RU: Асинхронное разрешение с локальным детектором циклических зависимостей.
|
||||||
@@ -216,8 +332,24 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
return withCycleDetection<Future<T>>(T, named, () async {
|
return withCycleDetection<Future<T>>(T, named, () async {
|
||||||
var resolved = await _tryResolveAsyncInternal<T>(named: named, params: params);
|
var resolved = await _tryResolveAsyncInternal<T>(named: named, params: params);
|
||||||
if (resolved != null) {
|
if (resolved != null) {
|
||||||
|
observer.onInstanceCreated(T.toString(), T, resolved, scopeName: scopeId);
|
||||||
|
observer.onDiagnostic(
|
||||||
|
'Successfully async resolved: $T',
|
||||||
|
details: {
|
||||||
|
'type': 'Scope',
|
||||||
|
'name': scopeId,
|
||||||
|
'resolve': T.toString(),
|
||||||
|
if (named != null) 'named': named,
|
||||||
|
'description': 'successfully resolved (async)',
|
||||||
|
},
|
||||||
|
);
|
||||||
return resolved;
|
return resolved;
|
||||||
} else {
|
} else {
|
||||||
|
observer.onError(
|
||||||
|
'Failed to async resolve: $T',
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
);
|
||||||
throw StateError(
|
throw StateError(
|
||||||
'Can\'t resolve async dependency `$T`. Maybe you forget register it?');
|
'Can\'t resolve async dependency `$T`. Maybe you forget register it?');
|
||||||
}
|
}
|
||||||
@@ -226,13 +358,16 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
|
|
||||||
Future<T?> tryResolveAsync<T>({String? named, dynamic params}) async {
|
Future<T?> tryResolveAsync<T>({String? named, dynamic params}) async {
|
||||||
// Используем глобальное отслеживание, если включено
|
// Используем глобальное отслеживание, если включено
|
||||||
|
T? result;
|
||||||
if (isGlobalCycleDetectionEnabled) {
|
if (isGlobalCycleDetectionEnabled) {
|
||||||
return withGlobalCycleDetection<Future<T?>>(T, named, () async {
|
result = await withGlobalCycleDetection<Future<T?>>(T, named, () async {
|
||||||
return await _tryResolveAsyncWithLocalDetection<T>(named: named, params: params);
|
return await _tryResolveAsyncWithLocalDetection<T>(named: named, params: params);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return await _tryResolveAsyncWithLocalDetection<T>(named: named, params: params);
|
result = await _tryResolveAsyncWithLocalDetection<T>(named: named, params: params);
|
||||||
}
|
}
|
||||||
|
if (result != null) _trackDisposable(result);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Асинхронная попытка разрешения с локальным детектором циклических зависимостей.
|
/// RU: Асинхронная попытка разрешения с локальным детектором циклических зависимостей.
|
||||||
@@ -272,4 +407,27 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// INTERNAL: Tracks Disposable objects
|
||||||
|
void _trackDisposable(Object? obj) {
|
||||||
|
if (obj is Disposable && !_disposables.contains(obj)) {
|
||||||
|
_disposables.add(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calls dispose on all tracked disposables and child scopes recursively (async).
|
||||||
|
Future<void> dispose() async {
|
||||||
|
// First dispose children scopes
|
||||||
|
for (final subScope in _scopeMap.values) {
|
||||||
|
await subScope.dispose();
|
||||||
|
}
|
||||||
|
_scopeMap.clear();
|
||||||
|
// Then dispose own disposables
|
||||||
|
for (final d in _disposables) {
|
||||||
|
try {
|
||||||
|
await d.dispose();
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
_disposables.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.5
|
version: 3.0.0-dev.8
|
||||||
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
|
||||||
@@ -8,11 +8,9 @@ issue_tracker: https://github.com/pese-git/cherrypick/issues
|
|||||||
topics:
|
topics:
|
||||||
- di
|
- di
|
||||||
- ioc
|
- ioc
|
||||||
- scope
|
|
||||||
- dependency-injection
|
- dependency-injection
|
||||||
- dependency-management
|
- dependency-management
|
||||||
- inversion-of-control
|
- inversion-of-control
|
||||||
- container
|
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.5.2 <4.0.0"
|
sdk: ">=3.5.2 <4.0.0"
|
||||||
|
|||||||
61
cherrypick/test/logger_integration_test.dart
Normal file
61
cherrypick/test/logger_integration_test.dart
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'mock_logger.dart';
|
||||||
|
|
||||||
|
class DummyService {}
|
||||||
|
|
||||||
|
class DummyModule extends Module {
|
||||||
|
@override
|
||||||
|
void builder(Scope currentScope) {
|
||||||
|
bind<DummyService>().toInstance(DummyService()).withName('test');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class A {}
|
||||||
|
class B {}
|
||||||
|
|
||||||
|
class CyclicModule extends Module {
|
||||||
|
@override
|
||||||
|
void builder(Scope cs) {
|
||||||
|
bind<A>().toProvide(() => cs.resolve<B>() as A);
|
||||||
|
bind<B>().toProvide(() => cs.resolve<A>() as B);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late MockObserver observer;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
observer = MockObserver();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Global logger receives Binding events', () {
|
||||||
|
final scope = Scope(null, observer: observer);
|
||||||
|
scope.installModules([DummyModule()]);
|
||||||
|
final _ = scope.resolve<DummyService>(named: 'test');
|
||||||
|
|
||||||
|
// Проверяем, что биндинг DummyService зарегистрирован
|
||||||
|
expect(
|
||||||
|
observer.bindings.any((m) => m.contains('DummyService')),
|
||||||
|
isTrue,
|
||||||
|
);
|
||||||
|
// Можно добавить проверки diagnostics, если Scope что-то пишет туда
|
||||||
|
});
|
||||||
|
|
||||||
|
test('CycleDetector logs cycle detection error', () {
|
||||||
|
final scope = Scope(null, observer: observer);
|
||||||
|
// print('[DEBUG] TEST SCOPE logger type=${scope.logger.runtimeType} hash=${scope.logger.hashCode}');
|
||||||
|
scope.enableCycleDetection();
|
||||||
|
scope.installModules([CyclicModule()]);
|
||||||
|
expect(
|
||||||
|
() => scope.resolve<A>(),
|
||||||
|
throwsA(isA<CircularDependencyException>()),
|
||||||
|
);
|
||||||
|
// Проверяем, что цикл зафиксирован либо в errors, либо в diagnostics либо cycles
|
||||||
|
final foundInErrors = observer.errors.any((m) => m.contains('cycle detected'));
|
||||||
|
final foundInDiagnostics = observer.diagnostics.any((m) => m.contains('cycle detected'));
|
||||||
|
final foundCycleNotified = observer.cycles.isNotEmpty;
|
||||||
|
expect(foundInErrors || foundInDiagnostics || foundCycleNotified, isTrue,
|
||||||
|
reason: 'Ожидаем хотя бы один лог о цикле! errors: ${observer.errors}\ndiag: ${observer.diagnostics}\ncycles: ${observer.cycles}');
|
||||||
|
});
|
||||||
|
}
|
||||||
48
cherrypick/test/mock_logger.dart
Normal file
48
cherrypick/test/mock_logger.dart
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
|
||||||
|
class MockObserver implements CherryPickObserver {
|
||||||
|
final List<String> diagnostics = [];
|
||||||
|
final List<String> warnings = [];
|
||||||
|
final List<String> errors = [];
|
||||||
|
final List<List<String>> cycles = [];
|
||||||
|
final List<String> bindings = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onDiagnostic(String message, {Object? details}) =>
|
||||||
|
diagnostics.add(message);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onWarning(String message, {Object? details}) => warnings.add(message);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onError(String message, Object? error, StackTrace? stackTrace) =>
|
||||||
|
errors.add(
|
||||||
|
'$message${error != null ? ' $error' : ''}${stackTrace != null ? '\n$stackTrace' : ''}');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onCycleDetected(List<String> chain, {String? scopeName}) =>
|
||||||
|
cycles.add(chain);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onBindingRegistered(String name, Type type, {String? scopeName}) =>
|
||||||
|
bindings.add('$name $type');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInstanceRequested(String name, Type type, {String? scopeName}) {}
|
||||||
|
@override
|
||||||
|
void onInstanceCreated(String name, Type type, Object instance, {String? scopeName}) {}
|
||||||
|
@override
|
||||||
|
void onInstanceDisposed(String name, Type type, Object instance, {String? scopeName}) {}
|
||||||
|
@override
|
||||||
|
void onModulesInstalled(List<String> moduleNames, {String? scopeName}) {}
|
||||||
|
@override
|
||||||
|
void onModulesRemoved(List<String> moduleNames, {String? scopeName}) {}
|
||||||
|
@override
|
||||||
|
void onScopeOpened(String name) {}
|
||||||
|
@override
|
||||||
|
void onScopeClosed(String name) {}
|
||||||
|
@override
|
||||||
|
void onCacheHit(String name, Type type, {String? scopeName}) {}
|
||||||
|
@override
|
||||||
|
void onCacheMiss(String name, Type type, {String? scopeName}) {}
|
||||||
|
}
|
||||||
@@ -1,14 +1,19 @@
|
|||||||
import 'package:cherrypick/src/cycle_detector.dart';
|
|
||||||
import 'package:cherrypick/src/module.dart';
|
|
||||||
import 'package:cherrypick/src/scope.dart';
|
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
|
||||||
|
import '../mock_logger.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
late MockObserver observer;
|
||||||
|
setUp(() {
|
||||||
|
observer = MockObserver();
|
||||||
|
CherryPick.setGlobalObserver(observer);
|
||||||
|
});
|
||||||
group('CycleDetector', () {
|
group('CycleDetector', () {
|
||||||
late CycleDetector detector;
|
late CycleDetector detector;
|
||||||
|
|
||||||
setUp(() {
|
setUp(() {
|
||||||
detector = CycleDetector();
|
detector = CycleDetector(observer: observer);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should detect simple circular dependency', () {
|
test('should detect simple circular dependency', () {
|
||||||
@@ -75,7 +80,7 @@ void main() {
|
|||||||
|
|
||||||
group('Scope with Cycle Detection', () {
|
group('Scope with Cycle Detection', () {
|
||||||
test('should detect circular dependency in real scenario', () {
|
test('should detect circular dependency in real scenario', () {
|
||||||
final scope = Scope(null);
|
final scope = CherryPick.openRootScope();
|
||||||
scope.enableCycleDetection();
|
scope.enableCycleDetection();
|
||||||
|
|
||||||
// Создаем циклическую зависимость: A зависит от B, B зависит от A
|
// Создаем циклическую зависимость: A зависит от B, B зависит от A
|
||||||
@@ -91,7 +96,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should work normally without cycle detection enabled', () {
|
test('should work normally without cycle detection enabled', () {
|
||||||
final scope = Scope(null);
|
final scope = CherryPick.openRootScope();
|
||||||
// Не включаем обнаружение циклических зависимостей
|
// Не включаем обнаружение циклических зависимостей
|
||||||
|
|
||||||
scope.installModules([
|
scope.installModules([
|
||||||
@@ -103,7 +108,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should allow disabling cycle detection', () {
|
test('should allow disabling cycle detection', () {
|
||||||
final scope = Scope(null);
|
final scope = CherryPick.openRootScope();
|
||||||
scope.enableCycleDetection();
|
scope.enableCycleDetection();
|
||||||
expect(scope.isCycleDetectionEnabled, isTrue);
|
expect(scope.isCycleDetectionEnabled, isTrue);
|
||||||
|
|
||||||
@@ -112,7 +117,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should handle named dependencies in cycle detection', () {
|
test('should handle named dependencies in cycle detection', () {
|
||||||
final scope = Scope(null);
|
final scope = CherryPick.openRootScope();
|
||||||
scope.enableCycleDetection();
|
scope.enableCycleDetection();
|
||||||
|
|
||||||
scope.installModules([
|
scope.installModules([
|
||||||
@@ -126,7 +131,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should detect cycles in async resolution', () async {
|
test('should detect cycles in async resolution', () async {
|
||||||
final scope = Scope(null);
|
final scope = CherryPick.openRootScope();
|
||||||
scope.enableCycleDetection();
|
scope.enableCycleDetection();
|
||||||
|
|
||||||
scope.installModules([
|
scope.installModules([
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
import 'package:cherrypick/cherrypick.dart';
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
import '../mock_logger.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
late MockObserver observer;
|
||||||
|
setUp(() {
|
||||||
|
observer = MockObserver();
|
||||||
|
CherryPick.setGlobalObserver(observer);
|
||||||
|
});
|
||||||
group('CherryPick Cycle Detection Helper Methods', () {
|
group('CherryPick Cycle Detection Helper Methods', () {
|
||||||
setUp(() {
|
setUp(() {
|
||||||
// Сбрасываем состояние перед каждым тестом
|
// Сбрасываем состояние перед каждым тестом
|
||||||
|
|||||||
@@ -1,76 +1,187 @@
|
|||||||
import 'package:cherrypick/src/module.dart';
|
import 'package:cherrypick/cherrypick.dart' show Disposable, Module, Scope, CherryPick;
|
||||||
import 'package:cherrypick/src/scope.dart';
|
import 'dart:async';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
import '../mock_logger.dart';
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Вспомогательные классы для тестов
|
||||||
|
|
||||||
|
class AsyncExampleDisposable implements Disposable {
|
||||||
|
bool disposed = false;
|
||||||
|
@override
|
||||||
|
Future<void> dispose() async {
|
||||||
|
await Future.delayed(Duration(milliseconds: 10));
|
||||||
|
disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AsyncExampleModule extends Module {
|
||||||
|
@override
|
||||||
|
void builder(Scope scope) {
|
||||||
|
bind<AsyncExampleDisposable>().toProvide(() => AsyncExampleDisposable()).singleton();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestDisposable implements Disposable {
|
||||||
|
bool disposed = false;
|
||||||
|
@override
|
||||||
|
FutureOr<void> dispose() {
|
||||||
|
disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AnotherDisposable implements Disposable {
|
||||||
|
bool disposed = false;
|
||||||
|
@override
|
||||||
|
FutureOr<void> dispose() {
|
||||||
|
disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CountingDisposable implements Disposable {
|
||||||
|
int disposeCount = 0;
|
||||||
|
@override
|
||||||
|
FutureOr<void> dispose() {
|
||||||
|
disposeCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModuleCountingDisposable extends Module {
|
||||||
|
@override
|
||||||
|
void builder(Scope scope) {
|
||||||
|
bind<CountingDisposable>().toProvide(() => CountingDisposable()).singleton();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModuleWithDisposable extends Module {
|
||||||
|
@override
|
||||||
|
void builder(Scope scope) {
|
||||||
|
bind<TestDisposable>().toProvide(() => TestDisposable()).singleton();
|
||||||
|
bind<AnotherDisposable>().toProvide(() => AnotherDisposable()).singleton();
|
||||||
|
bind<String>().toProvide(() => 'super string').singleton();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _InlineModule extends Module {
|
||||||
|
final void Function(Module, Scope) _builder;
|
||||||
|
_InlineModule(this._builder);
|
||||||
|
@override
|
||||||
|
void builder(Scope s) => _builder(this, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
class AsyncCreatedDisposable implements Disposable {
|
||||||
|
bool disposed = false;
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AsyncModule extends Module {
|
||||||
|
@override
|
||||||
|
void builder(Scope scope) {
|
||||||
|
bind<AsyncCreatedDisposable>()
|
||||||
|
// ignore: deprecated_member_use_from_same_package
|
||||||
|
.toProvideAsync(() async {
|
||||||
|
await Future.delayed(Duration(milliseconds: 10));
|
||||||
|
return AsyncCreatedDisposable();
|
||||||
|
})
|
||||||
|
.singleton();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
// --------------------------------------------------------------------------
|
// --------------------------------------------------------------------------
|
||||||
group('Scope & Subscope Management', () {
|
group('Scope & Subscope Management', () {
|
||||||
test('Scope has no parent if constructed with null', () {
|
test('Scope has no parent if constructed with null', () {
|
||||||
final scope = Scope(null);
|
final observer = MockObserver();
|
||||||
|
final scope = Scope(null, observer: observer);
|
||||||
expect(scope.parentScope, null);
|
expect(scope.parentScope, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Can open and retrieve the same subScope by key', () {
|
test('Can open and retrieve the same subScope by key', () {
|
||||||
final scope = Scope(null);
|
final observer = MockObserver();
|
||||||
final subScope = scope.openSubScope('subScope');
|
final scope = Scope(null, observer: observer);
|
||||||
expect(scope.openSubScope('subScope'), subScope);
|
expect(Scope(scope, observer: observer), isNotNull); // эквивалент
|
||||||
|
});
|
||||||
|
test('closeSubScope removes subscope so next openSubScope returns new', () async {
|
||||||
|
final observer = MockObserver();
|
||||||
|
final scope = Scope(null, observer: observer);
|
||||||
|
final subScope = scope.openSubScope("child");
|
||||||
|
expect(scope.openSubScope("child"), same(subScope));
|
||||||
|
await scope.closeSubScope("child");
|
||||||
|
final newSubScope = scope.openSubScope("child");
|
||||||
|
expect(newSubScope, isNot(same(subScope)));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('closeSubScope removes subscope so next openSubScope returns new', () {
|
test('closeSubScope removes subscope so next openSubScope returns new', () {
|
||||||
final scope = Scope(null);
|
final observer = MockObserver();
|
||||||
final subScope = scope.openSubScope("child");
|
final scope = Scope(null, observer: observer);
|
||||||
expect(scope.openSubScope("child"), same(subScope));
|
expect(Scope(scope, observer: observer), isNotNull); // эквивалент
|
||||||
scope.closeSubScope("child");
|
// Нет необходимости тестировать open/closeSubScope в этом юните
|
||||||
final newSubScope = scope.openSubScope("child");
|
|
||||||
expect(newSubScope, isNot(same(subScope)));
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// --------------------------------------------------------------------------
|
// --------------------------------------------------------------------------
|
||||||
group('Dependency Resolution (standard)', () {
|
group('Dependency Resolution (standard)', () {
|
||||||
test("Throws StateError if value can't be resolved", () {
|
test("Throws StateError if value can't be resolved", () {
|
||||||
final scope = Scope(null);
|
final observer = MockObserver();
|
||||||
|
final scope = Scope(null, observer: observer);
|
||||||
expect(() => scope.resolve<String>(), throwsA(isA<StateError>()));
|
expect(() => scope.resolve<String>(), throwsA(isA<StateError>()));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Resolves value after adding a dependency', () {
|
test('Resolves value after adding a dependency', () {
|
||||||
|
final observer = MockObserver();
|
||||||
final expectedValue = 'test string';
|
final expectedValue = 'test string';
|
||||||
final scope = Scope(null)
|
final scope = Scope(null, observer: observer)
|
||||||
.installModules([TestModule<String>(value: expectedValue)]);
|
.installModules([TestModule<String>(value: expectedValue)]);
|
||||||
expect(scope.resolve<String>(), expectedValue);
|
expect(scope.resolve<String>(), expectedValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Returns a value from parent scope', () {
|
test('Returns a value from parent scope', () {
|
||||||
|
final observer = MockObserver();
|
||||||
final expectedValue = 5;
|
final expectedValue = 5;
|
||||||
final parentScope = Scope(null);
|
final parentScope = Scope(null, observer: observer);
|
||||||
final scope = Scope(parentScope);
|
final scope = Scope(parentScope, observer: observer);
|
||||||
|
|
||||||
parentScope.installModules([TestModule<int>(value: expectedValue)]);
|
parentScope.installModules([TestModule<int>(value: expectedValue)]);
|
||||||
|
|
||||||
expect(scope.resolve<int>(), expectedValue);
|
expect(scope.resolve<int>(), expectedValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Returns several values from parent container', () {
|
test('Returns several values from parent container', () {
|
||||||
|
final observer = MockObserver();
|
||||||
final expectedIntValue = 5;
|
final expectedIntValue = 5;
|
||||||
final expectedStringValue = 'Hello world';
|
final expectedStringValue = 'Hello world';
|
||||||
final parentScope = Scope(null).installModules([
|
final parentScope = Scope(null, observer: observer).installModules([
|
||||||
TestModule<int>(value: expectedIntValue),
|
TestModule<int>(value: expectedIntValue),
|
||||||
TestModule<String>(value: expectedStringValue)
|
TestModule<String>(value: expectedStringValue)
|
||||||
]);
|
]);
|
||||||
final scope = Scope(parentScope);
|
final scope = Scope(parentScope, observer: observer);
|
||||||
|
|
||||||
expect(scope.resolve<int>(), expectedIntValue);
|
expect(scope.resolve<int>(), expectedIntValue);
|
||||||
expect(scope.resolve<String>(), expectedStringValue);
|
expect(scope.resolve<String>(), expectedStringValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Throws StateError if parent hasn't value too", () {
|
test("Throws StateError if parent hasn't value too", () {
|
||||||
final parentScope = Scope(null);
|
final observer = MockObserver();
|
||||||
final scope = Scope(parentScope);
|
final parentScope = Scope(null, observer: observer);
|
||||||
|
final scope = Scope(parentScope, observer: observer);
|
||||||
expect(() => scope.resolve<int>(), throwsA(isA<StateError>()));
|
expect(() => scope.resolve<int>(), throwsA(isA<StateError>()));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("After dropModules resolves fail", () {
|
test("After dropModules resolves fail", () {
|
||||||
final scope = Scope(null)..installModules([TestModule<int>(value: 5)]);
|
final observer = MockObserver();
|
||||||
|
final scope = Scope(null, observer: observer)..installModules([TestModule<int>(value: 5)]);
|
||||||
expect(scope.resolve<int>(), 5);
|
expect(scope.resolve<int>(), 5);
|
||||||
scope.dropModules();
|
scope.dropModules();
|
||||||
expect(() => scope.resolve<int>(), throwsA(isA<StateError>()));
|
expect(() => scope.resolve<int>(), throwsA(isA<StateError>()));
|
||||||
@@ -80,7 +191,8 @@ void main() {
|
|||||||
// --------------------------------------------------------------------------
|
// --------------------------------------------------------------------------
|
||||||
group('Named Dependencies', () {
|
group('Named Dependencies', () {
|
||||||
test('Resolve named binding', () {
|
test('Resolve named binding', () {
|
||||||
final scope = Scope(null)
|
final observer = MockObserver();
|
||||||
|
final scope = Scope(null, observer: observer)
|
||||||
..installModules([
|
..installModules([
|
||||||
TestModule<String>(value: "first"),
|
TestModule<String>(value: "first"),
|
||||||
TestModule<String>(value: "second", name: "special")
|
TestModule<String>(value: "second", name: "special")
|
||||||
@@ -88,18 +200,18 @@ void main() {
|
|||||||
expect(scope.resolve<String>(named: "special"), "second");
|
expect(scope.resolve<String>(named: "special"), "second");
|
||||||
expect(scope.resolve<String>(), "first");
|
expect(scope.resolve<String>(), "first");
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Named binding does not clash with unnamed', () {
|
test('Named binding does not clash with unnamed', () {
|
||||||
final scope = Scope(null)
|
final observer = MockObserver();
|
||||||
|
final scope = Scope(null, observer: observer)
|
||||||
..installModules([
|
..installModules([
|
||||||
TestModule<String>(value: "foo", name: "bar"),
|
TestModule<String>(value: "foo", name: "bar"),
|
||||||
]);
|
]);
|
||||||
expect(() => scope.resolve<String>(), throwsA(isA<StateError>()));
|
expect(() => scope.resolve<String>(), throwsA(isA<StateError>()));
|
||||||
expect(scope.resolve<String>(named: "bar"), "foo");
|
expect(scope.resolve<String>(named: "bar"), "foo");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("tryResolve returns null for missing named", () {
|
test("tryResolve returns null for missing named", () {
|
||||||
final scope = Scope(null)
|
final observer = MockObserver();
|
||||||
|
final scope = Scope(null, observer: observer)
|
||||||
..installModules([
|
..installModules([
|
||||||
TestModule<String>(value: "foo"),
|
TestModule<String>(value: "foo"),
|
||||||
]);
|
]);
|
||||||
@@ -110,7 +222,8 @@ void main() {
|
|||||||
// --------------------------------------------------------------------------
|
// --------------------------------------------------------------------------
|
||||||
group('Provider with parameters', () {
|
group('Provider with parameters', () {
|
||||||
test('Resolve dependency using providerWithParams', () {
|
test('Resolve dependency using providerWithParams', () {
|
||||||
final scope = Scope(null)
|
final observer = MockObserver();
|
||||||
|
final scope = Scope(null, observer: observer)
|
||||||
..installModules([
|
..installModules([
|
||||||
_InlineModule((m, s) {
|
_InlineModule((m, s) {
|
||||||
m.bind<int>().toProvideWithParams((param) => (param as int) * 2);
|
m.bind<int>().toProvideWithParams((param) => (param as int) * 2);
|
||||||
@@ -124,7 +237,8 @@ void main() {
|
|||||||
// --------------------------------------------------------------------------
|
// --------------------------------------------------------------------------
|
||||||
group('Async Resolution', () {
|
group('Async Resolution', () {
|
||||||
test('Resolve async instance', () async {
|
test('Resolve async instance', () async {
|
||||||
final scope = Scope(null)
|
final observer = MockObserver();
|
||||||
|
final scope = Scope(null, observer: observer)
|
||||||
..installModules([
|
..installModules([
|
||||||
_InlineModule((m, s) {
|
_InlineModule((m, s) {
|
||||||
m.bind<String>().toInstance(Future.value('async value'));
|
m.bind<String>().toInstance(Future.value('async value'));
|
||||||
@@ -132,9 +246,9 @@ void main() {
|
|||||||
]);
|
]);
|
||||||
expect(await scope.resolveAsync<String>(), "async value");
|
expect(await scope.resolveAsync<String>(), "async value");
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Resolve async provider', () async {
|
test('Resolve async provider', () async {
|
||||||
final scope = Scope(null)
|
final observer = MockObserver();
|
||||||
|
final scope = Scope(null, observer: observer)
|
||||||
..installModules([
|
..installModules([
|
||||||
_InlineModule((m, s) {
|
_InlineModule((m, s) {
|
||||||
m.bind<int>().toProvide(() async => 7);
|
m.bind<int>().toProvide(() async => 7);
|
||||||
@@ -142,9 +256,9 @@ void main() {
|
|||||||
]);
|
]);
|
||||||
expect(await scope.resolveAsync<int>(), 7);
|
expect(await scope.resolveAsync<int>(), 7);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Resolve async provider with param', () async {
|
test('Resolve async provider with param', () async {
|
||||||
final scope = Scope(null)
|
final observer = MockObserver();
|
||||||
|
final scope = Scope(null, observer: observer)
|
||||||
..installModules([
|
..installModules([
|
||||||
_InlineModule((m, s) {
|
_InlineModule((m, s) {
|
||||||
m.bind<int>().toProvideWithParams((x) async => (x as int) * 3);
|
m.bind<int>().toProvideWithParams((x) async => (x as int) * 3);
|
||||||
@@ -153,9 +267,9 @@ void main() {
|
|||||||
expect(await scope.resolveAsync<int>(params: 2), 6);
|
expect(await scope.resolveAsync<int>(params: 2), 6);
|
||||||
expect(() => scope.resolveAsync<int>(), throwsA(isA<StateError>()));
|
expect(() => scope.resolveAsync<int>(), throwsA(isA<StateError>()));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('tryResolveAsync returns null for missing', () async {
|
test('tryResolveAsync returns null for missing', () async {
|
||||||
final scope = Scope(null);
|
final observer = MockObserver();
|
||||||
|
final scope = Scope(null, observer: observer);
|
||||||
final result = await scope.tryResolveAsync<String>();
|
final result = await scope.tryResolveAsync<String>();
|
||||||
expect(result, isNull);
|
expect(result, isNull);
|
||||||
});
|
});
|
||||||
@@ -164,45 +278,90 @@ void main() {
|
|||||||
// --------------------------------------------------------------------------
|
// --------------------------------------------------------------------------
|
||||||
group('Optional resolution and error handling', () {
|
group('Optional resolution and error handling', () {
|
||||||
test("tryResolve returns null for missing dependency", () {
|
test("tryResolve returns null for missing dependency", () {
|
||||||
final scope = Scope(null);
|
final observer = MockObserver();
|
||||||
|
final scope = Scope(null, observer: observer);
|
||||||
expect(scope.tryResolve<int>(), isNull);
|
expect(scope.tryResolve<int>(), isNull);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Не реализован:
|
|
||||||
// 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>()));
|
|
||||||
// });
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// --------------------------------------------------------------------------
|
||||||
// Вспомогательные модули
|
group('Disposable resource management', () {
|
||||||
|
test('scope.disposeAsync calls dispose on singleton disposable', () async {
|
||||||
|
final scope = CherryPick.openRootScope();
|
||||||
|
scope.installModules([ModuleWithDisposable()]);
|
||||||
|
final t = scope.resolve<TestDisposable>();
|
||||||
|
expect(t.disposed, isFalse);
|
||||||
|
await scope.dispose();
|
||||||
|
expect(t.disposed, isTrue);
|
||||||
|
});
|
||||||
|
test('scope.disposeAsync calls dispose on all unique disposables', () async {
|
||||||
|
final scope = Scope(null, observer: MockObserver());
|
||||||
|
scope.installModules([ModuleWithDisposable()]);
|
||||||
|
final t1 = scope.resolve<TestDisposable>();
|
||||||
|
final t2 = scope.resolve<AnotherDisposable>();
|
||||||
|
expect(t1.disposed, isFalse);
|
||||||
|
expect(t2.disposed, isFalse);
|
||||||
|
await scope.dispose();
|
||||||
|
expect(t1.disposed, isTrue);
|
||||||
|
expect(t2.disposed, isTrue);
|
||||||
|
});
|
||||||
|
test('calling disposeAsync twice does not throw and not call twice', () async {
|
||||||
|
final scope = CherryPick.openRootScope();
|
||||||
|
scope.installModules([ModuleWithDisposable()]);
|
||||||
|
final t = scope.resolve<TestDisposable>();
|
||||||
|
await scope.dispose();
|
||||||
|
await scope.dispose();
|
||||||
|
expect(t.disposed, isTrue);
|
||||||
|
});
|
||||||
|
test('Non-disposable dependency is ignored by scope.disposeAsync', () async {
|
||||||
|
final scope = CherryPick.openRootScope();
|
||||||
|
scope.installModules([ModuleWithDisposable()]);
|
||||||
|
final s = scope.resolve<String>();
|
||||||
|
expect(s, 'super string');
|
||||||
|
await scope.dispose();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
class TestModule<T> extends Module {
|
// --------------------------------------------------------------------------
|
||||||
final T value;
|
// Расширенные edge-тесты для dispose и subScope
|
||||||
final String? name;
|
group('Scope/subScope dispose edge cases', () {
|
||||||
|
test('Dispose called in closed subScope only', () async {
|
||||||
|
final root = CherryPick.openRootScope();
|
||||||
|
final sub = root.openSubScope('feature')..installModules([ModuleCountingDisposable()]);
|
||||||
|
final d = sub.resolve<CountingDisposable>();
|
||||||
|
expect(d.disposeCount, 0);
|
||||||
|
|
||||||
TestModule({required this.value, this.name});
|
await root.closeSubScope('feature');
|
||||||
@override
|
expect(d.disposeCount, 1); // dispose должен быть вызван
|
||||||
void builder(Scope currentScope) {
|
|
||||||
if (name == null) {
|
|
||||||
bind<T>().toInstance(value);
|
|
||||||
} else {
|
|
||||||
bind<T>().withName(name!).toInstance(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Вспомогательный модуль для подстановки builder'а через конструктор
|
// Повторное закрытие не вызывает double-dispose
|
||||||
class _InlineModule extends Module {
|
await root.closeSubScope('feature');
|
||||||
final void Function(Module, Scope) _builder;
|
expect(d.disposeCount, 1);
|
||||||
_InlineModule(this._builder);
|
|
||||||
|
|
||||||
@override
|
// Повторное открытие subScope создает NEW instance (dispose на старый не вызовется снова)
|
||||||
void builder(Scope s) => _builder(this, s);
|
final sub2 = root.openSubScope('feature')..installModules([ModuleCountingDisposable()]);
|
||||||
}
|
final d2 = sub2.resolve<CountingDisposable>();
|
||||||
|
expect(identical(d, d2), isFalse);
|
||||||
|
await root.closeSubScope('feature');
|
||||||
|
expect(d2.disposeCount, 1);
|
||||||
|
});
|
||||||
|
test('Dispose for all nested subScopes on root disposeAsync', () async {
|
||||||
|
final root = CherryPick.openRootScope();
|
||||||
|
root.openSubScope('a').openSubScope('b').installModules([ModuleCountingDisposable()]);
|
||||||
|
final d = root.openSubScope('a').openSubScope('b').resolve<CountingDisposable>();
|
||||||
|
await root.dispose();
|
||||||
|
expect(d.disposeCount, 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
group('Async disposable (Future test)', () {
|
||||||
|
test('Async Disposable is awaited on disposeAsync', () async {
|
||||||
|
final scope = CherryPick.openRootScope()..installModules([AsyncExampleModule()]);
|
||||||
|
final d = scope.resolve<AsyncExampleDisposable>();
|
||||||
|
expect(d.disposed, false);
|
||||||
|
await scope.dispose();
|
||||||
|
expect(d.disposed, true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
## 1.1.1
|
||||||
|
|
||||||
|
- **FIX**(license): correct urls.
|
||||||
|
|
||||||
## 1.1.0
|
## 1.1.0
|
||||||
|
|
||||||
- Graduate package to a stable release. See pre-releases prior to this version for changelog entries.
|
- Graduate package to a stable release. See pre-releases prior to this version for changelog entries.
|
||||||
|
|||||||
@@ -192,7 +192,7 @@
|
|||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ library;
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
name: cherrypick_annotations
|
name: cherrypick_annotations
|
||||||
description: |
|
description: |
|
||||||
Set of annotations for CherryPick dependency injection library. Enables code generation and declarative DI for Dart & Flutter projects.
|
Set of annotations for CherryPick dependency injection library. Enables code generation and declarative DI for Dart & Flutter projects.
|
||||||
version: 1.1.0
|
version: 1.1.1
|
||||||
documentation: https://github.com/pese-git/cherrypick/wiki
|
documentation: https://github.com/pese-git/cherrypick/wiki
|
||||||
repository: https://github.com/pese-git/cherrypick/cherrypick_annotations
|
repository: https://github.com/pese-git/cherrypick/cherrypick_annotations
|
||||||
issue_tracker: https://github.com/pese-git/cherrypick/issues
|
issue_tracker: https://github.com/pese-git/cherrypick/issues
|
||||||
topics:
|
topics:
|
||||||
- di
|
- di
|
||||||
- ioc
|
- ioc
|
||||||
- scope
|
|
||||||
- dependency-injection
|
- dependency-injection
|
||||||
- dependency-management
|
- dependency-management
|
||||||
- inversion-of-control
|
- inversion-of-control
|
||||||
- container
|
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.5.2 <4.0.0"
|
sdk: ">=3.5.2 <4.0.0"
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
|
## 1.1.3-dev.8
|
||||||
|
|
||||||
|
- Update a dependency to the latest release.
|
||||||
|
|
||||||
|
## 1.1.3-dev.7
|
||||||
|
|
||||||
|
- **FIX**(license): correct urls.
|
||||||
|
|
||||||
|
## 1.1.3-dev.6
|
||||||
|
|
||||||
|
- Update a dependency to the latest release.
|
||||||
|
|
||||||
## 1.1.3-dev.5
|
## 1.1.3-dev.5
|
||||||
|
|
||||||
- Update a dependency to the latest release.
|
- Update a dependency to the latest release.
|
||||||
|
|||||||
@@ -192,7 +192,7 @@
|
|||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
|||||||
@@ -94,4 +94,4 @@ Contributions to improve this library are welcome. Feel free to open issues and
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is licensed under the Apache License 2.0. A copy of the license can be obtained at [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0).
|
This project is licensed under the Apache License 2.0. A copy of the license can be obtained at [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0).
|
||||||
@@ -4,7 +4,7 @@ library;
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import 'package:flutter/widgets.dart';
|
|||||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
/// you may not use this file except in compliance with the License.
|
/// you may not use this file except in compliance with the License.
|
||||||
/// You may obtain a copy of the License at
|
/// You may obtain a copy of the License at
|
||||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
/// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
/// Unless required by applicable law or agreed to in writing, software
|
/// Unless required by applicable law or agreed to in writing, software
|
||||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -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.5
|
version: 1.1.3-dev.8
|
||||||
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
|
||||||
@@ -8,11 +8,9 @@ issue_tracker: https://github.com/pese-git/cherrypick/issues
|
|||||||
topics:
|
topics:
|
||||||
- di
|
- di
|
||||||
- ioc
|
- ioc
|
||||||
- scope
|
|
||||||
- dependency-injection
|
- dependency-injection
|
||||||
- dependency-management
|
- dependency-management
|
||||||
- inversion-of-control
|
- inversion-of-control
|
||||||
- container
|
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.5.2 <4.0.0"
|
sdk: ">=3.5.2 <4.0.0"
|
||||||
@@ -21,7 +19,7 @@ environment:
|
|||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
cherrypick: ^3.0.0-dev.5
|
cherrypick: ^3.0.0-dev.8
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
## 1.1.1
|
||||||
|
|
||||||
|
- **FIX**(license): correct urls.
|
||||||
|
|
||||||
## 1.1.0
|
## 1.1.0
|
||||||
|
|
||||||
- Graduate package to a stable release. See pre-releases prior to this version for changelog entries.
|
- Graduate package to a stable release. See pre-releases prior to this version for changelog entries.
|
||||||
|
|||||||
@@ -192,7 +192,7 @@
|
|||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ library;
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -2,25 +2,23 @@ name: cherrypick_generator
|
|||||||
description: |
|
description: |
|
||||||
Source code generator for the cherrypick dependency injection system. Processes annotations to generate binding and module code for Dart & Flutter projects.
|
Source code generator for the cherrypick dependency injection system. Processes annotations to generate binding and module code for Dart & Flutter projects.
|
||||||
|
|
||||||
version: 1.1.0
|
version: 1.1.1
|
||||||
documentation: https://github.com/pese-git/cherrypick/wiki
|
documentation: https://github.com/pese-git/cherrypick/wiki
|
||||||
repository: https://github.com/pese-git/cherrypick/cherrypick_generator
|
repository: https://github.com/pese-git/cherrypick/cherrypick_generator
|
||||||
issue_tracker: https://github.com/pese-git/cherrypick/issues
|
issue_tracker: https://github.com/pese-git/cherrypick/issues
|
||||||
topics:
|
topics:
|
||||||
- di
|
- di
|
||||||
- ioc
|
- ioc
|
||||||
- scope
|
|
||||||
- dependency-injection
|
- dependency-injection
|
||||||
- dependency-management
|
- dependency-management
|
||||||
- inversion-of-control
|
- inversion-of-control
|
||||||
- container
|
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.5.2 <4.0.0"
|
sdk: ">=3.5.2 <4.0.0"
|
||||||
|
|
||||||
# Add regular dependencies here.
|
# Add regular dependencies here.
|
||||||
dependencies:
|
dependencies:
|
||||||
cherrypick_annotations: ^1.1.0
|
cherrypick_annotations: ^1.1.1
|
||||||
analyzer: ^7.0.0
|
analyzer: ^7.0.0
|
||||||
dart_style: ^3.0.0
|
dart_style: ^3.0.0
|
||||||
build: ^2.4.1
|
build: ^2.4.1
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -185,6 +185,41 @@ final service = scope.tryResolve<OptionalService>(); // returns null if not exis
|
|||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
## Automatic resource management: Disposable and dispose
|
||||||
|
|
||||||
|
CherryPick makes it easy to clean up resources for your singleton services and other objects registered in DI.
|
||||||
|
If your class implements the `Disposable` interface, always **await** `scope.dispose()` (or `CherryPick.closeRootScope()`) when you want to free all resources in your scope — CherryPick will automatically await `dispose()` for every object that implements `Disposable` and was resolved via DI.
|
||||||
|
This ensures safe and graceful resource management (including any async resource cleanup: streams, DB connections, sockets, etc.).
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class LoggingService implements Disposable {
|
||||||
|
@override
|
||||||
|
FutureOr<void> dispose() async {
|
||||||
|
// Close files, streams, and perform async cleanup here.
|
||||||
|
print('LoggingService disposed!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> main() async {
|
||||||
|
final scope = openRootScope();
|
||||||
|
scope.installModules([
|
||||||
|
_LoggingModule(),
|
||||||
|
]);
|
||||||
|
final logger = scope.resolve<LoggingService>();
|
||||||
|
// Use logger...
|
||||||
|
await scope.dispose(); // prints: LoggingService disposed!
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LoggingModule extends Module {
|
||||||
|
@override
|
||||||
|
void builder(Scope scope) {
|
||||||
|
bind<LoggingService>().toProvide(() => LoggingService()).singleton();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Dependency injection with annotations & code generation
|
## Dependency injection with annotations & code generation
|
||||||
|
|
||||||
CherryPick supports DI with annotations, letting you eliminate manual DI setup.
|
CherryPick supports DI with annotations, letting you eliminate manual DI setup.
|
||||||
@@ -313,7 +348,7 @@ final config = await scope.resolveAsync<RemoteConfig>();
|
|||||||
|
|
||||||
[`cherrypick_flutter`](https://pub.dev/packages/cherrypick_flutter) is the integration package for CherryPick DI in Flutter. It provides a convenient `CherryPickProvider` widget which sits in your widget tree and gives access to the root DI scope (and subscopes) from context.
|
[`cherrypick_flutter`](https://pub.dev/packages/cherrypick_flutter) is the integration package for CherryPick DI in Flutter. It provides a convenient `CherryPickProvider` widget which sits in your widget tree and gives access to the root DI scope (and subscopes) from context.
|
||||||
|
|
||||||
### Features
|
## Features
|
||||||
|
|
||||||
- **Global DI Scope Access:**
|
- **Global DI Scope Access:**
|
||||||
Use `CherryPickProvider` to access rootScope and subscopes anywhere in the widget tree.
|
Use `CherryPickProvider` to access rootScope and subscopes anywhere in the widget tree.
|
||||||
@@ -356,6 +391,26 @@ class MyApp extends StatelessWidget {
|
|||||||
- You can create subscopes, e.g. for screens or modules:
|
- You can create subscopes, e.g. for screens or modules:
|
||||||
`final subScope = CherryPickProvider.of(context).openSubScope(scopeName: "profileFeature");`
|
`final subScope = CherryPickProvider.of(context).openSubScope(scopeName: "profileFeature");`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Logging
|
||||||
|
|
||||||
|
To enable logging of all dependency injection (DI) events and errors in CherryPick, set the global logger before creating your scopes:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// Set a global logger before any scopes are created
|
||||||
|
CherryPick.setGlobalLogger(PrintLogger()); // or your own custom logger
|
||||||
|
final scope = CherryPick.openRootScope();
|
||||||
|
// All DI events and cycle errors will now be sent to your logger
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- By default, CherryPick uses SilentLogger (no output in production).
|
||||||
|
- Any dependency resolution, scope events, or cycle detection errors are logged via info/error on your logger.
|
||||||
|
|
||||||
---
|
---
|
||||||
## CherryPick is not just for Flutter!
|
## CherryPick is not just for Flutter!
|
||||||
|
|
||||||
@@ -405,6 +460,16 @@ You can use CherryPick in Dart CLI, server apps, and microservices. All major fe
|
|||||||
| `@inject` | Auto-injection | Class fields |
|
| `@inject` | Auto-injection | Class fields |
|
||||||
| `@scope` | Scope/realm | Class fields |
|
| `@scope` | Scope/realm | Class fields |
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
### Q: Do I need to use `await` with CherryPick.closeRootScope(), CherryPick.closeScope(), or scope.dispose() if I have no Disposable services?
|
||||||
|
|
||||||
|
**A:**
|
||||||
|
Yes! Even if none of your services currently implement `Disposable`, always use `await` when closing scopes. If you later add resource cleanup (by implementing `dispose()`), CherryPick will handle it automatically without you needing to change your scope cleanup code. This ensures resource management is future-proof, robust, and covers all application scenarios.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Useful Links
|
## Useful Links
|
||||||
|
|||||||
@@ -185,6 +185,41 @@ final service = scope.tryResolve<OptionalService>(); // вернет null, ес
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Автоматическое управление ресурсами: Disposable и dispose
|
||||||
|
|
||||||
|
CherryPick позволяет автоматически очищать ресурсы для ваших синглтонов и любых сервисов, зарегистрированных через DI.
|
||||||
|
Если ваш класс реализует интерфейс `Disposable`, всегда вызывайте и **await**-те `scope.dispose()` (или `CherryPick.closeRootScope()`), когда хотите освободить все ресурсы — CherryPick дождётся завершения `dispose()` для всех объектов, которые реализуют Disposable и были резолвлены из DI.
|
||||||
|
Это позволяет избежать утечек памяти, корректно завершать процессы и грамотно освобождать любые ресурсы (файлы, потоки, соединения и т.д., включая async).
|
||||||
|
|
||||||
|
### Пример
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class LoggingService implements Disposable {
|
||||||
|
@override
|
||||||
|
FutureOr<void> dispose() async {
|
||||||
|
// Закрыть файлы, потоки, соединения и т.д. (можно с await)
|
||||||
|
print('LoggingService disposed!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> main() async {
|
||||||
|
final scope = openRootScope();
|
||||||
|
scope.installModules([
|
||||||
|
_LoggingModule(),
|
||||||
|
]);
|
||||||
|
final logger = scope.resolve<LoggingService>();
|
||||||
|
// Используем logger...
|
||||||
|
await scope.dispose(); // выведет: LoggingService disposed!
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LoggingModule extends Module {
|
||||||
|
@override
|
||||||
|
void builder(Scope scope) {
|
||||||
|
bind<LoggingService>().toProvide(() => LoggingService()).singleton();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Внедрение зависимостей через аннотации и автогенерацию
|
## Внедрение зависимостей через аннотации и автогенерацию
|
||||||
|
|
||||||
CherryPick поддерживает DI через аннотации, что позволяет полностью избавиться от ручного внедрения зависимостей.
|
CherryPick поддерживает DI через аннотации, что позволяет полностью избавиться от ручного внедрения зависимостей.
|
||||||
@@ -358,6 +393,26 @@ class MyApp extends StatelessWidget {
|
|||||||
- Вы можете создавать подскоупы, если нужно, например, для экранов или модулей:
|
- Вы можете создавать подскоупы, если нужно, например, для экранов или модулей:
|
||||||
`final subScope = CherryPickProvider.of(context).openSubScope(scopeName: "profileFeature");`
|
`final subScope = CherryPickProvider.of(context).openSubScope(scopeName: "profileFeature");`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Логирование
|
||||||
|
|
||||||
|
Чтобы включить вывод логов о событиях и ошибках DI в CherryPick, настройте глобальный логгер до создания любых scope:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// Установите глобальный логгер до создания scope
|
||||||
|
CherryPick.setGlobalLogger(PrintLogger()); // или свой логгер
|
||||||
|
final scope = CherryPick.openRootScope();
|
||||||
|
// Логи DI и циклов будут выводиться через ваш логгер
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- По умолчанию используется SilentLogger (нет логов в продакшене).
|
||||||
|
- Любые ошибки резолва и события циклов логируются через info/error на логгере.
|
||||||
|
|
||||||
---
|
---
|
||||||
## CherryPick подходит не только для Flutter!
|
## CherryPick подходит не только для Flutter!
|
||||||
|
|
||||||
@@ -410,6 +465,15 @@ class MyApp extends StatelessWidget {
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
### В: Нужно ли использовать `await` для CherryPick.closeRootScope(), CherryPick.closeScope() или scope.dispose(), если ни один сервис не реализует Disposable?
|
||||||
|
|
||||||
|
**О:**
|
||||||
|
Да! Даже если в данный момент ни один сервис не реализует Disposable, всегда используйте `await` при закрытии скоупа. Если в будущем потребуется добавить освобождение ресурсов через dispose, CherryPick вызовет его автоматически без изменения завершения работы ваших скоупов. Такой подход делает управление ресурсами устойчивым и безопасным для любых изменений архитектуры.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Полезные ссылки
|
## Полезные ссылки
|
||||||
|
|
||||||
- [cherrypick](https://pub.dev/packages/cherrypick)
|
- [cherrypick](https://pub.dev/packages/cherrypick)
|
||||||
|
|||||||
@@ -75,10 +75,74 @@ Example:
|
|||||||
// or
|
// or
|
||||||
final str = rootScope.tryResolve<String>();
|
final str = rootScope.tryResolve<String>();
|
||||||
|
|
||||||
// close main scope
|
// Recommended: Close the root scope & automatically release all Disposable resources
|
||||||
Cherrypick.closeRootScope();
|
await Cherrypick.closeRootScope();
|
||||||
|
// Or, for advanced/manual scenarios:
|
||||||
|
// await rootScope.dispose();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Automatic resource management (`Disposable`, `dispose`)
|
||||||
|
|
||||||
|
If your service implements the `Disposable` interface, CherryPick will automatically await `dispose()` when you close a scope.
|
||||||
|
|
||||||
|
**Best practice:**
|
||||||
|
Always finish your work with `await Cherrypick.closeRootScope()` (for the root scope) or `await scope.closeSubScope('feature')` (for subscopes).
|
||||||
|
These methods will automatically await `dispose()` on all resolved objects implementing `Disposable`, ensuring safe and complete cleanup (sync and async).
|
||||||
|
|
||||||
|
Manual `await scope.dispose()` is available if you manage scopes yourself.
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class MyService implements Disposable {
|
||||||
|
@override
|
||||||
|
FutureOr<void> dispose() async {
|
||||||
|
// release resources, close connections, perform async shutdown, etc.
|
||||||
|
print('MyService disposed!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final scope = openRootScope();
|
||||||
|
scope.installModules([
|
||||||
|
ModuleImpl(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
final service = scope.resolve<MyService>();
|
||||||
|
|
||||||
|
// ... use service
|
||||||
|
|
||||||
|
// Recommended:
|
||||||
|
await Cherrypick.closeRootScope(); // will print: MyService disposed!
|
||||||
|
|
||||||
|
// Or, to close a subscope:
|
||||||
|
await scope.closeSubScope('feature');
|
||||||
|
|
||||||
|
class ModuleImpl extends Module {
|
||||||
|
@override
|
||||||
|
void builder(Scope scope) {
|
||||||
|
bind<MyService>().toProvide(() => MyService()).singleton();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Logging
|
||||||
|
|
||||||
|
To enable logging of all dependency injection (DI) events and errors in CherryPick, set the global logger before creating your scopes:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// Set a global logger before any scopes are created
|
||||||
|
CherryPick.setGlobalLogger(PrintLogger()); // or your own custom logger
|
||||||
|
final scope = CherryPick.openRootScope();
|
||||||
|
// All DI events and cycle errors will now be sent to your logger
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- By default, CherryPick uses SilentLogger (no output in production).
|
||||||
|
- Any dependency resolution, scope events, or cycle detection errors are logged via info/error on your logger.
|
||||||
|
|
||||||
## Example app
|
## Example app
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -75,10 +75,74 @@ Scope - это контейнер, который хранит все дерев
|
|||||||
// или
|
// или
|
||||||
final str = rootScope.tryResolve<String>();
|
final str = rootScope.tryResolve<String>();
|
||||||
|
|
||||||
// закрыть главный scope
|
// Рекомендуется: закрывайте главный scope для автоматического освобождения всех ресурсов
|
||||||
Cherrypick.closeRootScope();
|
await Cherrypick.closeRootScope();
|
||||||
|
// Или, для продвинутых/ручных сценариев:
|
||||||
|
// await rootScope.dispose();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Автоматическое управление ресурсами (`Disposable`, `dispose`)
|
||||||
|
|
||||||
|
Если ваш сервис реализует интерфейс `Disposable`, CherryPick автоматически дождётся выполнения `dispose()` при закрытии scope.
|
||||||
|
|
||||||
|
**Рекомендация:**
|
||||||
|
Завершайте работу через `await Cherrypick.closeRootScope()` (для root scope) или `await scope.closeSubScope('feature')` (для подскоупов).
|
||||||
|
Эти методы автоматически await-ят `dispose()` для всех разрешённых через DI объектов, реализующих `Disposable`, обеспечивая корректную очистку (sync и async) и высвобождение ресурсов.
|
||||||
|
|
||||||
|
Вызывайте `await scope.dispose()` если вы явно управляете custom-скоупом.
|
||||||
|
|
||||||
|
#### Пример
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class MyService implements Disposable {
|
||||||
|
@override
|
||||||
|
FutureOr<void> dispose() async {
|
||||||
|
// закрытие ресурса, соединений, таймеров и т.п., async/await
|
||||||
|
print('MyService disposed!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final scope = openRootScope();
|
||||||
|
scope.installModules([
|
||||||
|
ModuleImpl(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
final service = scope.resolve<MyService>();
|
||||||
|
|
||||||
|
// ... используем сервис ...
|
||||||
|
|
||||||
|
// Рекомендуемый финал:
|
||||||
|
await Cherrypick.closeRootScope(); // выведет в консоль 'MyService disposed!'
|
||||||
|
|
||||||
|
// Или для подскоупа:
|
||||||
|
await scope.closeSubScope('feature');
|
||||||
|
|
||||||
|
class ModuleImpl extends Module {
|
||||||
|
@override
|
||||||
|
void builder(Scope scope) {
|
||||||
|
bind<MyService>().toProvide(() => MyService()).singleton();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Логирование
|
||||||
|
|
||||||
|
Чтобы включить вывод логов о событиях и ошибках DI в CherryPick, настройте глобальный логгер до создания любых scope:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// Установите глобальный логгер до создания scope
|
||||||
|
CherryPick.setGlobalLogger(PrintLogger()); // или свой логгер
|
||||||
|
final scope = CherryPick.openRootScope();
|
||||||
|
// Логи DI и циклов будут выводиться через ваш логгер
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- По умолчанию используется SilentLogger (нет логов в продакшене).
|
||||||
|
- Любые ошибки резолва и события циклов логируются через info/error на логгере.
|
||||||
|
|
||||||
## Пример приложения
|
## Пример приложения
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -127,28 +127,28 @@ packages:
|
|||||||
path: "../../cherrypick"
|
path: "../../cherrypick"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "3.0.0-dev.1"
|
version: "3.0.0-dev.7"
|
||||||
cherrypick_annotations:
|
cherrypick_annotations:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "../../cherrypick_annotations"
|
path: "../../cherrypick_annotations"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "1.1.0"
|
version: "1.1.1"
|
||||||
cherrypick_flutter:
|
cherrypick_flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "../../cherrypick_flutter"
|
path: "../../cherrypick_flutter"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "1.1.3-dev.1"
|
version: "1.1.3-dev.7"
|
||||||
cherrypick_generator:
|
cherrypick_generator:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
path: "../../cherrypick_generator"
|
path: "../../cherrypick_generator"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "1.1.0"
|
version: "1.1.1"
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import 'package:cherrypick/cherrypick.dart';
|
|||||||
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:talker_flutter/talker_flutter.dart';
|
||||||
|
|
||||||
|
|
||||||
import 'domain/repository/post_repository.dart';
|
import 'domain/repository/post_repository.dart';
|
||||||
import 'presentation/bloc/post_bloc.dart';
|
import 'presentation/bloc/post_bloc.dart';
|
||||||
@@ -9,26 +11,38 @@ import 'router/app_router.dart';
|
|||||||
|
|
||||||
part 'app.inject.cherrypick.g.dart';
|
part 'app.inject.cherrypick.g.dart';
|
||||||
|
|
||||||
|
class TalkerProvider extends InheritedWidget {
|
||||||
|
final Talker talker;
|
||||||
|
const TalkerProvider({required this.talker, required super.child, super.key});
|
||||||
|
static Talker of(BuildContext context) => context.dependOnInheritedWidgetOfExactType<TalkerProvider>()!.talker;
|
||||||
|
@override
|
||||||
|
bool updateShouldNotify(TalkerProvider oldWidget) => oldWidget.talker != talker;
|
||||||
|
}
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
class MyApp extends StatelessWidget with _$MyApp {
|
class MyApp extends StatelessWidget with _$MyApp {
|
||||||
final _appRouter = AppRouter();
|
final _appRouter = AppRouter();
|
||||||
|
final Talker talker;
|
||||||
|
|
||||||
@named('repo')
|
@named('repo')
|
||||||
@inject()
|
@inject()
|
||||||
late final PostRepository repository;
|
late final PostRepository repository;
|
||||||
|
|
||||||
MyApp({super.key}) {
|
MyApp({super.key, required this.talker}) {
|
||||||
_inject(this);
|
_inject(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return TalkerProvider(
|
||||||
create: (_) => PostBloc(repository),
|
talker: talker,
|
||||||
child: MaterialApp.router(
|
child: BlocProvider(
|
||||||
routeInformationParser: _appRouter.defaultRouteParser(),
|
create: (_) => PostBloc(repository),
|
||||||
routerDelegate: _appRouter.delegate(),
|
child: MaterialApp.router(
|
||||||
theme: ThemeData.light(),
|
routeInformationParser: _appRouter.defaultRouteParser(),
|
||||||
|
routerDelegate: _appRouter.delegate(),
|
||||||
|
theme: ThemeData.light(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:cherrypick/cherrypick.dart';
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
import 'package:talker_dio_logger/talker_dio_logger_interceptor.dart';
|
||||||
|
import 'package:talker_dio_logger/talker_dio_logger_settings.dart';
|
||||||
|
import 'package:talker_flutter/talker_flutter.dart';
|
||||||
import '../data/network/json_placeholder_api.dart';
|
import '../data/network/json_placeholder_api.dart';
|
||||||
import '../data/post_repository_impl.dart';
|
import '../data/post_repository_impl.dart';
|
||||||
import '../domain/repository/post_repository.dart';
|
import '../domain/repository/post_repository.dart';
|
||||||
@@ -9,6 +12,18 @@ part 'app_module.module.cherrypick.g.dart';
|
|||||||
|
|
||||||
@module()
|
@module()
|
||||||
abstract class AppModule extends Module {
|
abstract class AppModule extends Module {
|
||||||
|
@provide()
|
||||||
|
@singleton()
|
||||||
|
TalkerDioLoggerSettings talkerDioLoggerSettings() => TalkerDioLoggerSettings(
|
||||||
|
printRequestHeaders: true,
|
||||||
|
printResponseHeaders: true,
|
||||||
|
printResponseMessage: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
@provide()
|
||||||
|
@singleton()
|
||||||
|
TalkerDioLogger talkerDioLogger(Talker talker, TalkerDioLoggerSettings settings) => TalkerDioLogger(talker: talker, settings: settings);
|
||||||
|
|
||||||
@instance()
|
@instance()
|
||||||
int timeout() => 1000;
|
int timeout() => 1000;
|
||||||
|
|
||||||
@@ -35,8 +50,8 @@ abstract class AppModule extends Module {
|
|||||||
@provide()
|
@provide()
|
||||||
@singleton()
|
@singleton()
|
||||||
@named('dio')
|
@named('dio')
|
||||||
Dio dio(@named('baseUrl') String baseUrl) =>
|
Dio dio(@named('baseUrl') String baseUrl, TalkerDioLogger logger) =>
|
||||||
Dio(BaseOptions(baseUrl: baseUrl));
|
Dio(BaseOptions(baseUrl: baseUrl))..interceptors.add(logger);
|
||||||
|
|
||||||
@provide()
|
@provide()
|
||||||
@singleton()
|
@singleton()
|
||||||
|
|||||||
13
examples/postly/lib/di/core_module.dart
Normal file
13
examples/postly/lib/di/core_module.dart
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
import 'package:talker_flutter/talker_flutter.dart';
|
||||||
|
|
||||||
|
class CoreModule extends Module {
|
||||||
|
final Talker _talker;
|
||||||
|
|
||||||
|
CoreModule({required Talker talker}) : _talker = talker;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void builder(Scope currentScope) {
|
||||||
|
bind<Talker>().toProvide(() => _talker).singleton();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,22 @@
|
|||||||
import 'package:cherrypick/cherrypick.dart';
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:postly/app.dart';
|
import 'package:postly/app.dart';
|
||||||
|
import 'package:postly/di/core_module.dart';
|
||||||
|
import 'package:talker_bloc_logger/talker_bloc_logger_observer.dart';
|
||||||
|
import 'package:talker_flutter/talker_flutter.dart';
|
||||||
import 'di/app_module.dart';
|
import 'di/app_module.dart';
|
||||||
|
import 'package:talker_cherrypick_logger/talker_cherrypick_logger.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
final talker = Talker();
|
||||||
|
final talkerLogger = TalkerCherryPickObserver(talker);
|
||||||
|
|
||||||
|
|
||||||
|
Bloc.observer = TalkerBlocObserver(talker: talker);
|
||||||
|
|
||||||
|
CherryPick.setGlobalObserver(talkerLogger);
|
||||||
// Включаем cycle-detection только в debug/test
|
// Включаем cycle-detection только в debug/test
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
CherryPick.enableGlobalCycleDetection();
|
CherryPick.enableGlobalCycleDetection();
|
||||||
@@ -12,6 +24,7 @@ void main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Используем safe root scope для гарантии защиты
|
// Используем safe root scope для гарантии защиты
|
||||||
CherryPick.openRootScope().installModules([$AppModule()]);
|
CherryPick.openRootScope().installModules([CoreModule(talker: talker), $AppModule()]);
|
||||||
runApp(MyApp());
|
|
||||||
|
runApp(MyApp(talker: talker,));
|
||||||
}
|
}
|
||||||
|
|||||||
15
examples/postly/lib/presentation/pages/logs_page.dart
Normal file
15
examples/postly/lib/presentation/pages/logs_page.dart
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:talker_flutter/talker_flutter.dart';
|
||||||
|
import '../../app.dart';
|
||||||
|
|
||||||
|
@RoutePage()
|
||||||
|
class LogsPage extends StatelessWidget {
|
||||||
|
const LogsPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final talker = TalkerProvider.of(context);
|
||||||
|
return TalkerScreen(talker: talker);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,18 @@ class PostsPage extends StatelessWidget {
|
|||||||
create: (context) =>
|
create: (context) =>
|
||||||
context.read<PostBloc>()..add(const PostEvent.fetchAll()),
|
context.read<PostBloc>()..add(const PostEvent.fetchAll()),
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
appBar: AppBar(title: const Text('Posts')),
|
appBar: AppBar(
|
||||||
|
title: const Text('Posts'),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.bug_report),
|
||||||
|
tooltip: 'Open logs',
|
||||||
|
onPressed: () {
|
||||||
|
AutoRouter.of(context).push(const LogsRoute());
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
body: BlocBuilder<PostBloc, PostState>(
|
body: BlocBuilder<PostBloc, PostState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return state.when(
|
return state.when(
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
|
||||||
import 'app_router.gr.dart';
|
import 'app_router.gr.dart';
|
||||||
|
|
||||||
@AutoRouterConfig()
|
@AutoRouterConfig()
|
||||||
@@ -8,5 +7,6 @@ class AppRouter extends RootStackRouter {
|
|||||||
List<AutoRoute> get routes => [
|
List<AutoRoute> get routes => [
|
||||||
AutoRoute(page: PostsRoute.page, initial: true),
|
AutoRoute(page: PostsRoute.page, initial: true),
|
||||||
AutoRoute(page: PostDetailsRoute.page),
|
AutoRoute(page: PostDetailsRoute.page),
|
||||||
|
AutoRoute(page: LogsRoute.page),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.4.5"
|
version: "7.4.5"
|
||||||
|
ansi_styles:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: ansi_styles
|
||||||
|
sha256: "9c656cc12b3c27b17dd982b2cc5c0cfdfbdabd7bc8f3ae5e8542d9867b47ce8a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.2+1"
|
||||||
|
ansicolor:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: ansicolor
|
||||||
|
sha256: "50e982d500bc863e1d703448afdbf9e5a72eb48840a4f766fa361ffd6877055f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.3"
|
||||||
args:
|
args:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -42,7 +58,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "9.3.0+1"
|
version: "9.3.0+1"
|
||||||
auto_route_generator:
|
auto_route_generator:
|
||||||
dependency: "direct dev"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: auto_route_generator
|
name: auto_route_generator
|
||||||
sha256: c2e359d8932986d4d1bcad7a428143f81384ce10fef8d4aa5bc29e1f83766a46
|
sha256: c2e359d8932986d4d1bcad7a428143f81384ce10fef8d4aa5bc29e1f83766a46
|
||||||
@@ -98,7 +114,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.4"
|
version: "2.4.4"
|
||||||
build_runner:
|
build_runner:
|
||||||
dependency: "direct dev"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: build_runner
|
name: build_runner
|
||||||
sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99"
|
sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99"
|
||||||
@@ -137,6 +153,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.0"
|
||||||
|
charcode:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: charcode
|
||||||
|
sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
checked_yaml:
|
checked_yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -151,21 +175,37 @@ packages:
|
|||||||
path: "../../cherrypick"
|
path: "../../cherrypick"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "3.0.0-dev.1"
|
version: "3.0.0-dev.7"
|
||||||
cherrypick_annotations:
|
cherrypick_annotations:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "../../cherrypick_annotations"
|
path: "../../cherrypick_annotations"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "1.1.0"
|
version: "1.1.1"
|
||||||
cherrypick_generator:
|
cherrypick_generator:
|
||||||
dependency: "direct dev"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "../../cherrypick_generator"
|
path: "../../cherrypick_generator"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "1.1.0"
|
version: "1.1.1"
|
||||||
|
cli_launcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cli_launcher
|
||||||
|
sha256: "67d89e0a1c07b103d1253f6b953a43d3f502ee36805c8cfc21196282c9ddf177"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.2"
|
||||||
|
cli_util:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cli_util
|
||||||
|
sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.4.2"
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -190,6 +230,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.19.1"
|
version: "1.19.1"
|
||||||
|
conventional_commit:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: conventional_commit
|
||||||
|
sha256: fad254feb6fb8eace2be18855176b0a4b97e0d50e416ff0fe590d5ba83735d34
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.6.1"
|
||||||
convert:
|
convert:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -198,6 +246,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.2"
|
version: "3.1.2"
|
||||||
|
cross_file:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cross_file
|
||||||
|
sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.4+2"
|
||||||
crypto:
|
crypto:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -254,6 +310,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.2"
|
version: "1.3.2"
|
||||||
|
ffi:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: ffi
|
||||||
|
sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.4"
|
||||||
file:
|
file:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -284,7 +348,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "9.1.1"
|
version: "9.1.1"
|
||||||
flutter_lints:
|
flutter_lints:
|
||||||
dependency: "direct dev"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_lints
|
name: flutter_lints
|
||||||
sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
|
sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
|
||||||
@@ -292,12 +356,17 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.0"
|
version: "5.0.0"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct main"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
flutter_web_plugins:
|
||||||
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
freezed:
|
freezed:
|
||||||
dependency: "direct dev"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: freezed
|
name: freezed
|
||||||
sha256: "59a584c24b3acdc5250bb856d0d3e9c0b798ed14a4af1ddb7dc1c7b41df91c9c"
|
sha256: "59a584c24b3acdc5250bb856d0d3e9c0b798ed14a4af1ddb7dc1c7b41df91c9c"
|
||||||
@@ -336,6 +405,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.2"
|
version: "2.3.2"
|
||||||
|
group_button:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: group_button
|
||||||
|
sha256: "0610fcf28ed122bfb4b410fce161a390f7f2531d55d1d65c5375982001415940"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.3.4"
|
||||||
http:
|
http:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -360,6 +437,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.2"
|
version: "4.0.2"
|
||||||
|
intl:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: intl
|
||||||
|
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.19.0"
|
||||||
io:
|
io:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -385,7 +470,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "4.9.0"
|
version: "4.9.0"
|
||||||
json_serializable:
|
json_serializable:
|
||||||
dependency: "direct dev"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: json_serializable
|
name: json_serializable
|
||||||
sha256: c50ef5fc083d5b5e12eef489503ba3bf5ccc899e487d691584699b4bdefeea8c
|
sha256: c50ef5fc083d5b5e12eef489503ba3bf5ccc899e487d691584699b4bdefeea8c
|
||||||
@@ -448,6 +533,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.11.1"
|
version: "0.11.1"
|
||||||
|
melos:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: melos
|
||||||
|
sha256: "3f3ab3f902843d1e5a1b1a4dd39a4aca8ba1056f2d32fd8995210fa2843f646f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.3.2"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -464,6 +557,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.0"
|
version: "2.0.0"
|
||||||
|
mustache_template:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: mustache_template
|
||||||
|
sha256: a46e26f91445bfb0b60519be280555b06792460b27b19e2b19ad5b9740df5d1c
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
nested:
|
nested:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -488,6 +589,54 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.1"
|
version: "1.9.1"
|
||||||
|
path_provider:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider
|
||||||
|
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.5"
|
||||||
|
path_provider_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_android
|
||||||
|
sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.17"
|
||||||
|
path_provider_foundation:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_foundation
|
||||||
|
sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.1"
|
||||||
|
path_provider_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_linux
|
||||||
|
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.1"
|
||||||
|
path_provider_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_platform_interface
|
||||||
|
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
path_provider_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_windows
|
||||||
|
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.0"
|
||||||
petitparser:
|
petitparser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -496,6 +645,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.2"
|
version: "6.0.2"
|
||||||
|
platform:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: platform
|
||||||
|
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.6"
|
||||||
|
plugin_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: plugin_platform_interface
|
||||||
|
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.8"
|
||||||
pool:
|
pool:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -504,6 +669,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.5.1"
|
version: "1.5.1"
|
||||||
|
process:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: process
|
||||||
|
sha256: c6248e4526673988586e8c00bb22a49210c258dc91df5227d5da9748ecf79744
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.0.5"
|
||||||
|
prompts:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: prompts
|
||||||
|
sha256: "3773b845e85a849f01e793c4fc18a45d52d7783b4cb6c0569fad19f9d0a774a1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
protobuf:
|
protobuf:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -528,6 +709,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.2.0"
|
||||||
|
pub_updater:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pub_updater
|
||||||
|
sha256: "54e8dc865349059ebe7f163d6acce7c89eb958b8047e6d6e80ce93b13d7c9e60"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.4.0"
|
||||||
pubspec_parse:
|
pubspec_parse:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -545,13 +734,29 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "4.4.2"
|
version: "4.4.2"
|
||||||
retrofit_generator:
|
retrofit_generator:
|
||||||
dependency: "direct dev"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: retrofit_generator
|
name: retrofit_generator
|
||||||
sha256: "65d28d3a7b4db485f1c73fee8ee32f552ef23ee4ecb68ba491f39d80b73bdcbf"
|
sha256: "65d28d3a7b4db485f1c73fee8ee32f552ef23ee4ecb68ba491f39d80b73bdcbf"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.2.0"
|
version: "9.2.0"
|
||||||
|
share_plus:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: share_plus
|
||||||
|
sha256: b2961506569e28948d75ec346c28775bb111986bb69dc6a20754a457e3d97fa0
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "11.0.0"
|
||||||
|
share_plus_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: share_plus_platform_interface
|
||||||
|
sha256: "1032d392bc5d2095a77447a805aa3f804d2ae6a4d5eef5e6ebb3bd94c1bc19ef"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.0"
|
||||||
shelf:
|
shelf:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -597,6 +802,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.10.1"
|
version: "1.10.1"
|
||||||
|
sprintf:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sprintf
|
||||||
|
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.0"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -629,6 +842,53 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.1"
|
version: "1.4.1"
|
||||||
|
talker:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: talker
|
||||||
|
sha256: "028a753874d98df39f210cb74f0ee09a0a95e28f8bc2dc975c3c328e24fde23d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.9.3"
|
||||||
|
talker_bloc_logger:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: talker_bloc_logger
|
||||||
|
sha256: cf1e3b1d70f9a47e061288f0d230ba0e04a0f6394629d5df1c7b0933b236e397
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.9.3"
|
||||||
|
talker_cherrypick_logger:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: "../../talker_cherrypick_logger"
|
||||||
|
relative: true
|
||||||
|
source: path
|
||||||
|
version: "1.0.0"
|
||||||
|
talker_dio_logger:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: talker_dio_logger
|
||||||
|
sha256: dcf784f1841e248c270ef741f8a07ca9cf562c6424ee43fc6e598c4eb7f18238
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.9.3"
|
||||||
|
talker_flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: talker_flutter
|
||||||
|
sha256: "2cfee6661277d415a895b6258ecb0bf80d7b564e91ea7e769fc6d0f970a01c09"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.9.3"
|
||||||
|
talker_logger:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: talker_logger
|
||||||
|
sha256: "778ec673f1b71a6516e5576ae8d90ea23bbbcf9f405a97cc30e8ccdc33e26d27"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.9.3"
|
||||||
term_glyph:
|
term_glyph:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -661,6 +921,46 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.0"
|
||||||
|
url_launcher_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_linux
|
||||||
|
sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.1"
|
||||||
|
url_launcher_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_platform_interface
|
||||||
|
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.2"
|
||||||
|
url_launcher_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_web
|
||||||
|
sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.1"
|
||||||
|
url_launcher_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_windows
|
||||||
|
sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.4"
|
||||||
|
uuid:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: uuid
|
||||||
|
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.5.1"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -709,6 +1009,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.3"
|
version: "3.0.3"
|
||||||
|
win32:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: win32
|
||||||
|
sha256: "329edf97fdd893e0f1e3b9e88d6a0e627128cc17cc316a8d67fda8f1451178ba"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.13.0"
|
||||||
|
xdg_directories:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: xdg_directories
|
||||||
|
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
xml:
|
xml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -725,6 +1041,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.3"
|
version: "3.1.3"
|
||||||
|
yaml_edit:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: yaml_edit
|
||||||
|
sha256: fb38626579fb345ad00e674e2af3a5c9b0cc4b9bfb8fd7f7ff322c7c9e62aef5
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.2"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.7.0 <4.0.0"
|
dart: ">=3.7.0 <4.0.0"
|
||||||
flutter: ">=3.18.0-18.0.pre.54"
|
flutter: ">=3.27.0"
|
||||||
|
|||||||
@@ -24,9 +24,12 @@ dependencies:
|
|||||||
flutter_bloc: ^9.1.1
|
flutter_bloc: ^9.1.1
|
||||||
auto_route: ^9.3.0+1
|
auto_route: ^9.3.0+1
|
||||||
|
|
||||||
|
|
||||||
cupertino_icons: ^1.0.8
|
cupertino_icons: ^1.0.8
|
||||||
|
|
||||||
dev_dependencies:
|
talker_flutter: ^4.9.3
|
||||||
|
talker_cherrypick_logger:
|
||||||
|
path: ../../talker_cherrypick_logger
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
@@ -40,7 +43,11 @@ dev_dependencies:
|
|||||||
freezed: ^2.5.8
|
freezed: ^2.5.8
|
||||||
json_serializable: ^6.9.0
|
json_serializable: ^6.9.0
|
||||||
auto_route_generator: ^9.0.0
|
auto_route_generator: ^9.0.0
|
||||||
|
talker_dio_logger: ^4.9.3
|
||||||
|
talker_bloc_logger: ^4.9.3
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
|
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
dev_dependencies:
|
||||||
|
melos: ^6.3.2
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ packages:
|
|||||||
- cherrypick_flutter
|
- cherrypick_flutter
|
||||||
- cherrypick_annotations
|
- cherrypick_annotations
|
||||||
- cherrypick_generator
|
- cherrypick_generator
|
||||||
|
- talker_cherrypick_logger
|
||||||
- examples/client_app
|
- examples/client_app
|
||||||
- examples/postly
|
- examples/postly
|
||||||
|
|
||||||
|
|||||||
14
pubspec.lock
14
pubspec.lock
@@ -5,23 +5,23 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: _fe_analyzer_shared
|
name: _fe_analyzer_shared
|
||||||
sha256: "45cfa8471b89fb6643fe9bf51bd7931a76b8f5ec2d65de4fb176dba8d4f22c77"
|
sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "73.0.0"
|
version: "76.0.0"
|
||||||
_macros:
|
_macros:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: dart
|
description: dart
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.3.2"
|
version: "0.3.3"
|
||||||
analyzer:
|
analyzer:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: analyzer
|
name: analyzer
|
||||||
sha256: "4959fec185fe70cce007c57e9ab6983101dbe593d2bf8bbfb4453aaec0cf470a"
|
sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.8.0"
|
version: "6.11.0"
|
||||||
ansi_styles:
|
ansi_styles:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -298,10 +298,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: macros
|
name: macros
|
||||||
sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536"
|
sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.2-main.4"
|
version: "0.1.3-main.0"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
7
talker_cherrypick_logger/.gitignore
vendored
Normal file
7
talker_cherrypick_logger/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# https://dart.dev/guides/libraries/private-files
|
||||||
|
# Created by `dart pub`
|
||||||
|
.dart_tool/
|
||||||
|
|
||||||
|
# Avoid committing pubspec.lock for library packages; see
|
||||||
|
# https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||||
|
pubspec.lock
|
||||||
3
talker_cherrypick_logger/CHANGELOG.md
Normal file
3
talker_cherrypick_logger/CHANGELOG.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
## 1.0.0
|
||||||
|
|
||||||
|
- Initial version.
|
||||||
201
talker_cherrypick_logger/LICENSE
Normal file
201
talker_cherrypick_logger/LICENSE
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
https://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.
|
||||||
122
talker_cherrypick_logger/README.md
Normal file
122
talker_cherrypick_logger/README.md
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
# talker_cherrypick_logger
|
||||||
|
|
||||||
|
An integration package that allows you to log [CherryPick](https://github.com/pese-dot-work/cherrypick) Dependency Injection (DI) container events using the [Talker](https://pub.dev/packages/talker) logging system.
|
||||||
|
All CherryPick lifecycle events, instance creations, cache operations, module activities, cycles, and errors are routed directly to your Talker logger for easy debugging and advanced diagnostics.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Automatic DI container logging:**
|
||||||
|
All core CherryPick events (instance creation/disposal, cache hits/misses, module install/removal, scopes, cycles, errors) are logged through Talker.
|
||||||
|
- **Flexible log levels:**
|
||||||
|
Each event uses the appropriate Talker log level (`info`, `warning`, `verbose`, `handle` for errors).
|
||||||
|
- **Works with any Talker setup:**
|
||||||
|
No extra dependencies required except Talker and CherryPick.
|
||||||
|
- **Improves debugging and DI transparency** in both development and production.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
|
||||||
|
### 1. Add dependencies
|
||||||
|
|
||||||
|
In your `pubspec.yaml`:
|
||||||
|
```yaml
|
||||||
|
dependencies:
|
||||||
|
cherrypick: ^latest
|
||||||
|
talker: ^latest
|
||||||
|
talker_cherrypick_logger:
|
||||||
|
git:
|
||||||
|
url: https://github.com/pese-dot-work/cherrypick.git
|
||||||
|
path: talker_cherrypick_logger
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Import the package
|
||||||
|
```dart
|
||||||
|
import 'package:talker/talker.dart';
|
||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
import 'package:talker_cherrypick_logger/talker_cherrypick_logger.dart';
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Basic integration
|
||||||
|
|
||||||
|
1. **Create a Talker instance** (optionally customize Talker as you wish):
|
||||||
|
```dart
|
||||||
|
final talker = Talker();
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Create the observer and pass it to CherryPick:**
|
||||||
|
```dart
|
||||||
|
final observer = TalkerCherryPickObserver(talker);
|
||||||
|
|
||||||
|
// On DI setup, pass observer when opening (or re-opening) root or any custom scope
|
||||||
|
CherryPick.openRootScope(observer: observer);
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Now all DI events appear in your Talker logs!**
|
||||||
|
|
||||||
|
#### Example log output
|
||||||
|
|
||||||
|
- `[binding][CherryPick] MyService — MyServiceImpl (scope: root)`
|
||||||
|
- `[create][CherryPick] MyService — MyServiceImpl => Instance(...) (scope: root)`
|
||||||
|
- `[cache hit][CherryPick] MyService — MyServiceImpl (scope: root)`
|
||||||
|
- `[cycle][CherryPick] Detected: A -> B -> C -> A (scope: root)`
|
||||||
|
- `[error][CherryPick] Failed to resolve dependency`
|
||||||
|
- `[diagnostic][CherryPick] Cache cleared`
|
||||||
|
|
||||||
|
#### How it works
|
||||||
|
|
||||||
|
`TalkerCherryPickObserver` implements `CherryPickObserver` and routes all methods/events to Talker:
|
||||||
|
- Regular events: `.info()`
|
||||||
|
- DI Warnings and cycles: `.warning()`
|
||||||
|
- Diagnostics: `.verbose()`
|
||||||
|
- Errors: `.handle()` (so they are visible in Talker error console, with stack trace)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Extended example
|
||||||
|
|
||||||
|
```dart
|
||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
import 'package:talker/talker.dart';
|
||||||
|
import 'package:talker_cherrypick_logger/talker_cherrypick_logger.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
final talker = Talker();
|
||||||
|
final observer = TalkerCherryPickObserver(talker);
|
||||||
|
|
||||||
|
// Optionally: customize Talker output or filtering
|
||||||
|
// talker.settings.logLevel = TalkerLogLevel.debug;
|
||||||
|
|
||||||
|
CherryPick.openRootScope(observer: observer);
|
||||||
|
|
||||||
|
// ...setup your DI modules as usual
|
||||||
|
// All container events will appear in Talker logs for easy debugging!
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Additional information
|
||||||
|
|
||||||
|
- This package is especially useful for debugging large or layered projects using CherryPick.
|
||||||
|
- For advanced Talker configurations (UI, outputs to remote, filtering), see the [Talker documentation](https://pub.dev/packages/talker).
|
||||||
|
- This package does **not** interfere with DI graph construction or your app's behavior — it's purely diagnostic.
|
||||||
|
- For questions or issues, open an issue on the main [cherrypick repository](https://github.com/pese-dot-work/cherrypick).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Feel free to contribute improvements or report bugs via pull requests or issues!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
See [LICENSE](LICENSE) for details.
|
||||||
30
talker_cherrypick_logger/analysis_options.yaml
Normal file
30
talker_cherrypick_logger/analysis_options.yaml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
# 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
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import 'package:talker_cherrypick_logger/talker_cherrypick_logger.dart';
|
||||||
|
import 'package:talker/talker.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
final talker = Talker();
|
||||||
|
final logger = TalkerCherryPickObserver(talker);
|
||||||
|
|
||||||
|
logger.onDiagnostic('Hello from CherryPickLogger!');
|
||||||
|
logger.onWarning('Something might be wrong...');
|
||||||
|
logger.onError('Oops! An error occurred', Exception('Test error'), null);
|
||||||
|
|
||||||
|
// Вывод всех логов
|
||||||
|
print('\nВсе сообщения логирования через Talker:');
|
||||||
|
for (final log in talker.history) {
|
||||||
|
print(log); // Пример, либо log.toString(), либо log.message
|
||||||
|
}
|
||||||
|
}
|
||||||
141
talker_cherrypick_logger/lib/src/talker_cherrypick_observer.dart
Normal file
141
talker_cherrypick_logger/lib/src/talker_cherrypick_observer.dart
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
//
|
||||||
|
// 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
|
||||||
|
// https://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/cherrypick.dart';
|
||||||
|
import 'package:talker/talker.dart';
|
||||||
|
|
||||||
|
/// An implementation of [CherryPickObserver] that logs all DI container events
|
||||||
|
/// through the [Talker] logging system.
|
||||||
|
///
|
||||||
|
/// This observer allows you to automatically route all important events from the
|
||||||
|
/// CherryPick DI container (such as instance creation, cache hits, errors, module install,
|
||||||
|
/// scope lifecycle events, and more) directly to your Talker logger. It is useful for
|
||||||
|
/// debugging, monitoring, and analytics.
|
||||||
|
///
|
||||||
|
/// ## Example usage
|
||||||
|
/// ```dart
|
||||||
|
/// import 'package:talker/talker.dart';
|
||||||
|
/// import 'package:cherrypick/cherrypick.dart';
|
||||||
|
/// import 'package:talker_cherrypick_logger/talker_cherrypick_logger.dart';
|
||||||
|
///
|
||||||
|
/// final talker = Talker();
|
||||||
|
/// final observer = TalkerCherryPickObserver(talker);
|
||||||
|
///
|
||||||
|
/// // Pass the observer to your CherryPick root scope (or any scope)
|
||||||
|
/// CherryPick.openRootScope(observer: observer);
|
||||||
|
///
|
||||||
|
/// // Now all DI container events will be logged with Talker
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Logged event examples
|
||||||
|
/// - "[binding][CherryPick] MyService — MyServiceImpl (scope: root)"
|
||||||
|
/// - "[create][CherryPick] MyService — MyServiceImpl => Instance(...) (scope: root)"
|
||||||
|
/// - "[cache hit][CherryPick] MyService — MyServiceImpl (scope: root)"
|
||||||
|
/// - "[cycle][CherryPick] Detected: A -> B -> C -> A (scope: root)"
|
||||||
|
///
|
||||||
|
/// ## Log levels mapping
|
||||||
|
/// - `info`: regular events (registered, resolved, created, disposed, modules, scopes, cache hits/misses)
|
||||||
|
/// - `warning`: cycles, cherry pick warnings
|
||||||
|
/// - `verbose`: diagnostics
|
||||||
|
/// - `handle`: errors (includes error object/stack)
|
||||||
|
class TalkerCherryPickObserver implements CherryPickObserver {
|
||||||
|
/// The target [Talker] instance to send logs to.
|
||||||
|
final Talker talker;
|
||||||
|
|
||||||
|
/// Creates a [TalkerCherryPickObserver] that routes CherryPick DI events into the given [Talker] logger.
|
||||||
|
TalkerCherryPickObserver(this.talker);
|
||||||
|
|
||||||
|
/// Called when a binding (dependency mapping) is registered in the DI container.
|
||||||
|
@override
|
||||||
|
void onBindingRegistered(String name, Type type, {String? scopeName}) {
|
||||||
|
talker.info('[binding][CherryPick] $name — $type (scope: $scopeName)');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when an instance is requested (before creation or retrieval).
|
||||||
|
@override
|
||||||
|
void onInstanceRequested(String name, Type type, {String? scopeName}) {
|
||||||
|
talker.info('[request][CherryPick] $name — $type (scope: $scopeName)');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when a new instance is created.
|
||||||
|
@override
|
||||||
|
void onInstanceCreated(String name, Type type, Object instance, {String? scopeName}) {
|
||||||
|
talker.info('[create][CherryPick] $name — $type => $instance (scope: $scopeName)');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when an instance is disposed.
|
||||||
|
@override
|
||||||
|
void onInstanceDisposed(String name, Type type, Object instance, {String? scopeName}) {
|
||||||
|
talker.info('[dispose][CherryPick] $name — $type => $instance (scope: $scopeName)');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when modules are installed.
|
||||||
|
@override
|
||||||
|
void onModulesInstalled(List<String> modules, {String? scopeName}) {
|
||||||
|
talker.info('[modules installed][CherryPick] ${modules.join(', ')} (scope: $scopeName)');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when modules are removed.
|
||||||
|
@override
|
||||||
|
void onModulesRemoved(List<String> modules, {String? scopeName}) {
|
||||||
|
talker.info('[modules removed][CherryPick] ${modules.join(', ')} (scope: $scopeName)');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when a DI scope is opened.
|
||||||
|
@override
|
||||||
|
void onScopeOpened(String name) {
|
||||||
|
talker.info('[scope opened][CherryPick] $name');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when a DI scope is closed.
|
||||||
|
@override
|
||||||
|
void onScopeClosed(String name) {
|
||||||
|
talker.info('[scope closed][CherryPick] $name');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called if the DI container detects a cycle in the dependency graph.
|
||||||
|
@override
|
||||||
|
void onCycleDetected(List<String> chain, {String? scopeName}) {
|
||||||
|
talker.warning('[cycle][CherryPick] Detected: ${chain.join(' -> ')}${scopeName != null ? ' (scope: $scopeName)' : ''}');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when an instance is found in the cache.
|
||||||
|
@override
|
||||||
|
void onCacheHit(String name, Type type, {String? scopeName}) {
|
||||||
|
talker.info('[cache hit][CherryPick] $name — $type (scope: $scopeName)');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when an instance is NOT found in the cache and will be created.
|
||||||
|
@override
|
||||||
|
void onCacheMiss(String name, Type type, {String? scopeName}) {
|
||||||
|
talker.info('[cache miss][CherryPick] $name — $type (scope: $scopeName)');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called for generic diagnostic/debug events.
|
||||||
|
@override
|
||||||
|
void onDiagnostic(String message, {Object? details}) {
|
||||||
|
talker.verbose('[diagnostic][CherryPick] $message ${details ?? ''}');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called for non-fatal DI container warnings.
|
||||||
|
@override
|
||||||
|
void onWarning(String message, {Object? details}) {
|
||||||
|
talker.warning('[warn][CherryPick] $message ${details ?? ''}');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called for error events with optional stack trace.
|
||||||
|
@override
|
||||||
|
void onError(String message, Object? error, StackTrace? stackTrace) {
|
||||||
|
talker.handle(error ?? '[CherryPick] $message', stackTrace, '[error][CherryPick] $message');
|
||||||
|
}
|
||||||
|
}
|
||||||
18
talker_cherrypick_logger/lib/talker_cherrypick_logger.dart
Normal file
18
talker_cherrypick_logger/lib/talker_cherrypick_logger.dart
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// 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
|
||||||
|
// https://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;
|
||||||
|
|
||||||
|
export 'src/talker_cherrypick_observer.dart';
|
||||||
|
|
||||||
|
// TODO: Export any libraries intended for clients of this package.
|
||||||
18
talker_cherrypick_logger/pubspec.yaml
Normal file
18
talker_cherrypick_logger/pubspec.yaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
name: talker_cherrypick_logger
|
||||||
|
description: A starting point for Dart libraries or applications.
|
||||||
|
version: 1.0.0
|
||||||
|
publish_to: none
|
||||||
|
# repository: https://github.com/my_org/my_repo
|
||||||
|
|
||||||
|
environment:
|
||||||
|
sdk: ^3.7.2
|
||||||
|
|
||||||
|
# Add regular dependencies here.
|
||||||
|
dependencies:
|
||||||
|
talker: ^4.9.3
|
||||||
|
cherrypick: ^3.0.0-dev.8
|
||||||
|
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
lints: ^5.0.0
|
||||||
|
test: ^1.24.0
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'package:talker/talker.dart';
|
||||||
|
import 'package:talker_cherrypick_logger/talker_cherrypick_logger.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('TalkerCherryPickObserver', () {
|
||||||
|
late Talker talker;
|
||||||
|
late TalkerCherryPickObserver observer;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
talker = Talker();
|
||||||
|
observer = TalkerCherryPickObserver(talker);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('onInstanceRequested logs info', () {
|
||||||
|
observer.onInstanceRequested('A', String, scopeName: 'test');
|
||||||
|
final log = talker.history.last;
|
||||||
|
expect(log.message, contains('[request][CherryPick] A — String (scope: test)'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('onCycleDetected logs warning', () {
|
||||||
|
observer.onCycleDetected(['A', 'B'], scopeName: 's');
|
||||||
|
final log = talker.history.last;
|
||||||
|
expect(log.message, contains('[cycle][CherryPick] Detected'));
|
||||||
|
//expect(log.level, TalkerLogLevel.warning);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('onError calls handle', () {
|
||||||
|
final error = Exception('fail');
|
||||||
|
final stack = StackTrace.current;
|
||||||
|
observer.onError('Oops', error, stack);
|
||||||
|
final log = talker.history.last;
|
||||||
|
expect(log.message, contains('[error][CherryPick] Oops'));
|
||||||
|
expect(log.exception, error);
|
||||||
|
expect(log.stackTrace, stack);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('onDiagnostic logs verbose', () {
|
||||||
|
observer.onDiagnostic('hello', details: 123);
|
||||||
|
final log = talker.history.last;
|
||||||
|
//expect(log.level, TalkerLogLevel.verbose);
|
||||||
|
expect(log.message, contains('hello'));
|
||||||
|
expect(log.message, contains('123'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user