Compare commits

..

12 Commits

Author SHA1 Message Date
Sergey Penkovsky
d93d4173a2 chore(release): publish packages
- cherrypick@3.0.0-dev.7
 - cherrypick_annotations@1.1.1
 - cherrypick_flutter@1.1.3-dev.7
 - cherrypick_generator@1.1.1
2025-08-11 12:38:17 +03:00
Sergey Penkovsky
85aa23d7ed Update README.md 2025-08-11 12:35:02 +03:00
Sergey Penkovsky
51cf4a0dc0 docs(readme): add comprehensive section on annotations and DI code generation
- Added 'Using Annotations & Code Generation' section explaining DI annotations flow.
- Included full table of supported annotations, practical usage examples, setup steps, troubleshooting, and references.
- Improves onboarding for CherryPick users using @injectable, @module, @provide, @named, @scope, @params and related features.

See doc/annotations_en.md and generator/module READMEs for extended docs.
2025-08-11 12:24:44 +03:00
Sergey Penkovsky
8f980ff111 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. 2025-08-11 12:02:32 +03:00
Sergey Penkovsky
4d872d7c25 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. 2025-08-11 11:53:25 +03:00
Sergey Penkovsky
450f4231cb Merge pull request #14 from pese-git/dispose
Dispose
2025-08-11 11:40:57 +03:00
Sergey Penkovsky
cd1b9cf49d fix(comment): fix warnings 2025-08-08 23:42:35 +03:00
Sergey Penkovsky
33775f5748 fix(license): correct urls 2025-08-08 23:24:05 +03:00
Sergey Penkovsky
e5848784ac refactor(core): make closeRootScope async and await dispose
BREAKING CHANGE: closeRootScope is now async (returns Future<void> and must be awaited). This change improves resource lifecycle management by allowing asynchronous cleanup when disposing the root Scope. All usages of closeRootScope should be updated to use await.

- Change closeRootScope from void to Future<void> and add async keyword
- Await _rootScopefor correct async disposal
- Ensures proper disposal and better future extensibility for async resources

Closes #xxx (replace if issue exists)
2025-08-08 16:56:37 +03:00
Sergey Penkovsky
a4b0ddfa54 docs(faq): add best practice FAQ about using await with scope disposal
- Added FAQ section in documentation (README and tutorials, EN + RU) recommending always using await when calling CherryPick.closeRootScope, scope.closeScope, or scope.dispose, even if no services implement Disposable.
- Clarifies future-proof resource management for all users.
2025-08-08 16:08:33 +03:00
Sergey Penkovsky
547a15fa4e docs(faq): add best practice FAQ about using await with scope disposal
- Added FAQ section in documentation (README and tutorials, EN + RU) recommending always using await when calling CherryPick.closeRootScope, scope.closeScope, or scope.dispose, even if no services implement Disposable.
- Clarifies future-proof resource management for all users.
2025-08-08 16:08:33 +03:00
Sergey Penkovsky
a9c95f6a89 docs+feat: add Disposable interface source and usage example
feat(core,doc): unified async dispose mechanism for resource cleanup

BREAKING CHANGE:

- Added full support for asynchronous resource cleanup via a unified FutureOr<void> dispose() method in the Disposable interface.
- The Scope now provides only Future<void> dispose() for disposing all tracked resources and child scopes (sync-only dispose() was removed).
- All calls to cleanup in code and tests (scope itself, subscopes, and custom modules) now require await ...dispose().
- Documentation and all examples updated: resource management is always async and must be awaited; Disposable implementers may use both sync and async cleanup.
- Old-style, synchronous cleanup methods have been completely removed (API is now consistently async for all DI lifecycle management).
- Example and tutorial code now demonstrate async resource disposal patterns.
2025-08-08 16:08:29 +03:00
65 changed files with 1015 additions and 129 deletions

View File

@@ -3,6 +3,49 @@
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-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 ## 2025-08-08
### Changes ### Changes

View File

@@ -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,

View File

@@ -47,7 +47,7 @@ packages:
path: "../cherrypick" path: "../cherrypick"
relative: true relative: true
source: path source: path
version: "3.0.0-dev.3" version: "3.0.0-dev.5"
collection: collection:
dependency: transitive dependency: transitive
description: description:

View File

@@ -1,3 +1,17 @@
## 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 ## 3.0.0-dev.6
> Note: This release has breaking changes. > Note: This release has breaking changes.

View File

@@ -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,

View File

@@ -1,8 +1,92 @@
# 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)
- [Automatic Resource Cleanup with Disposable](#automatic-resource-cleanup-with-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)
- [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
@@ -79,8 +163,108 @@ 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();
```
---
### Automatic Resource Cleanup with 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
class CacheManager implements Disposable {
void dispose() {
cache.clear();
print('CacheManager disposed!');
}
}
final scope = CherryPick.openRootScope();
scope.installModules([
Module((bind) => bind<CacheManager>().toProvide(() => CacheManager()).singleton()),
]);
// ...later
await CherryPick.closeRootScope(); // prints: CacheManager disposed!
```
#### Async Example
```dart
class MyServiceWithSocket implements Disposable {
@override
Future<void> dispose() async {
await socket.close();
print('Socket closed!');
}
}
scope.installModules([
Module((bind) => bind<MyServiceWithSocket>().toProvide(() => MyServiceWithSocket()).singleton()),
]);
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.
---
### Automatic resource management (`Disposable`, `dispose`)
CherryPick automatically manages the lifecycle of any object registered via DI that implements the `Disposable` interface.
**Best practice:**
Always finish your work with `await CherryPick.closeRootScope()` (for the root scope) or `await scope.closeSubScope('key')` (for subscopes).
These methods will automatically await `dispose()` on all resolved objects (e.g., singletons) that implement `Disposable`, ensuring proper and complete resource cleanup—sync or async.
Manual `await scope.dispose()` may be useful if you manually manage custom scopes.
#### Example
```dart
class MyService implements Disposable {
@override
FutureOr<void> dispose() async {
// release resources, close streams, perform async shutdown, etc.
print('MyService disposed!');
}
}
final scope = openRootScope();
scope.installModules([
ModuleImpl(),
]);
final service = scope.resolve<MyService>();
// ... use service
// Recommended completion:
await CherryPick.closeRootScope(); // will print: MyService disposed!
// Or, to close and clean up a subscope and its resources:
await scope.closeSubScope('feature');
class ModuleImpl extends Module {
@override
void builder(Scope scope) {
bind<MyService>().toProvide(() => MyService()).singleton();
}
}
``` ```
#### Working with Subscopes #### Working with Subscopes
@@ -101,6 +285,144 @@ final dataBloc = await subScope.resolveAsync<DataBloc>();
> >
> This optimization is internal and does not change any library APIs or usage patterns, but it significantly improves resolution speed in larger applications. > This optimization is internal and does not change any library APIs or usage patterns, but it significantly improves resolution speed in larger applications.
---
## 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 `_5ProfilePage` 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.
---
### Dependency Lookup API ### Dependency Lookup API
- `resolve<T>()` — Locates a dependency instance or throws if missing. - `resolve<T>()` — Locates a dependency instance or throws if missing.
@@ -271,6 +593,7 @@ scope.installModules([...]);
- [x] Null-safe Resolution (tryResolve/tryResolveAsync) - [x] Null-safe Resolution (tryResolve/tryResolveAsync)
- [x] Circular Dependency Detection (Local and Global) - [x] Circular Dependency Detection (Local and Global)
- [x] Comprehensive logging of dependency injection state and actions - [x] Comprehensive logging of dependency injection state and actions
- [x] Automatic resource cleanup for all registered Disposable dependencies
## Quick Guide: Circular Dependency Detection ## Quick Guide: Circular Dependency Detection
@@ -340,6 +663,14 @@ try {
**More details:** See [cycle_detection.en.md](doc/cycle_detection.en.md) **More details:** See [cycle_detection.en.md](doc/cycle_detection.en.md)
## 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.
## Documentation ## Documentation
- [Circular Dependency Detection (English)](doc/cycle_detection.en.md) - [Circular Dependency Detection (English)](doc/cycle_detection.en.md)
@@ -351,7 +682,7 @@ Contributions are welcome! Please open issues or submit pull requests on [GitHub
## 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).
--- ---

View 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();
}
}

View File

@@ -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.
@@ -21,3 +21,4 @@ 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/logger.dart'; export 'package:cherrypick/src/logger.dart';
export 'package:cherrypick/src/disposable.dart';

View File

@@ -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,8 +13,8 @@
import 'package:cherrypick/src/binding_resolver.dart'; import 'package:cherrypick/src/binding_resolver.dart';
/// RU: Класс Binding<T> настраивает параметры экземпляра. /// RU: Класс Binding&lt;T&gt; настраивает параметры экземпляра.
/// ENG: The Binding<T> class configures the settings for the instance. /// ENG: The Binding&lt;T&gt; class configures the settings for the instance.
/// ///
import 'package:cherrypick/src/logger.dart'; import 'package:cherrypick/src/logger.dart';
import 'package:cherrypick/src/log_format.dart'; import 'package:cherrypick/src/log_format.dart';

View File

@@ -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>;

View File

@@ -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.

View 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();
}

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.
@@ -94,9 +94,12 @@ class CherryPick {
/// ```dart /// ```dart
/// CherryPick.closeRootScope(); /// CherryPick.closeRootScope();
/// ``` /// ```
static void closeRootScope() { static Future<void> closeRootScope() async {
if (_rootScope != null) {
await _rootScope!.dispose(); // Автоматический вызов dispose для rootScope!
_rootScope = null; _rootScope = null;
} }
}
/// Globally enables cycle detection for all new [Scope]s created by CherryPick. /// Globally enables cycle detection for all new [Scope]s created by CherryPick.
/// ///
@@ -249,9 +252,9 @@ class CherryPick {
/// CherryPick.closeScope(scopeName: 'network.super.api'); /// CherryPick.closeScope(scopeName: 'network.super.api');
/// ``` /// ```
@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; return;
} }
final nameParts = scopeName.split(separator); final nameParts = scopeName.split(separator);
@@ -264,9 +267,9 @@ class CherryPick {
openRootScope(), openRootScope(),
(Scope previous, String element) => previous.openSubScope(element) (Scope previous, String element) => previous.openSubScope(element)
); );
scope.closeSubScope(lastPart); await scope.closeSubScope(lastPart);
} else { } else {
openRootScope().closeSubScope(nameParts.first); await openRootScope().closeSubScope(nameParts.first);
} }
} }

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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,6 +14,7 @@ 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';
@@ -28,6 +29,9 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
@override @override
CherryPickLogger get logger => _logger; CherryPickLogger get logger => _logger;
/// 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].
@@ -94,14 +98,15 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
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!);
@@ -175,9 +180,10 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
/// ///
T resolve<T>({String? named, dynamic params}) { T resolve<T>({String? named, dynamic params}) {
// Используем глобальное отслеживание, если включено // Используем глобальное отслеживание, если включено
T result;
if (isGlobalCycleDetectionEnabled) { if (isGlobalCycleDetectionEnabled) {
try { try {
return withGlobalCycleDetection<T>(T, named, () { result = withGlobalCycleDetection<T>(T, named, () {
return _resolveWithLocalDetection<T>(named: named, params: params); return _resolveWithLocalDetection<T>(named: named, params: params);
}); });
} catch (e, s) { } catch (e, s) {
@@ -195,7 +201,7 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
} }
} else { } else {
try { try {
return _resolveWithLocalDetection<T>(named: named, params: params); result = _resolveWithLocalDetection<T>(named: named, params: params);
} catch (e, s) { } catch (e, s) {
logger.error( logger.error(
formatLogMessage( formatLogMessage(
@@ -210,6 +216,8 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
rethrow; rethrow;
} }
} }
_trackDisposable(result);
return result;
} }
/// RU: Разрешение с локальным детектором циклических зависимостей. /// RU: Разрешение с локальным детектором циклических зависимостей.
@@ -251,13 +259,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: Попытка разрешения с локальным детектором циклических зависимостей.
@@ -295,13 +306,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: Асинхронное разрешение с локальным детектором циклических зависимостей.
@@ -320,13 +334,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: Асинхронная попытка разрешения с локальным детектором циклических зависимостей.
@@ -366,4 +383,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();
}
} }

View File

@@ -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.6 version: 3.0.0-dev.7
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

View File

@@ -1,7 +1,111 @@
import 'package:cherrypick/cherrypick.dart'; import 'package:cherrypick/cherrypick.dart' show Disposable, Module, Scope, CherryPick;
import 'dart:async';
import 'package:test/test.dart'; import 'package:test/test.dart';
import '../mock_logger.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', () {
@@ -10,12 +114,20 @@ void main() {
final scope = Scope(null, logger: logger); final scope = Scope(null, logger: logger);
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 logger = MockLogger(); final logger = MockLogger();
final scope = Scope(null, logger: logger); final scope = Scope(null, logger: logger);
expect(Scope(scope, logger: logger), isNotNull); // эквивалент expect(Scope(scope, logger: logger), isNotNull); // эквивалент
}); });
test('closeSubScope removes subscope so next openSubScope returns new', () async {
final logger = MockLogger();
final scope = Scope(null, logger: logger);
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 logger = MockLogger(); final logger = MockLogger();
@@ -32,7 +144,6 @@ void main() {
final scope = Scope(null, logger: logger); final scope = Scope(null, logger: logger);
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 logger = MockLogger(); final logger = MockLogger();
final expectedValue = 'test string'; final expectedValue = 'test string';
@@ -40,7 +151,6 @@ void main() {
.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 logger = MockLogger(); final logger = MockLogger();
final expectedValue = 5; final expectedValue = 5;
@@ -48,10 +158,8 @@ void main() {
final scope = Scope(parentScope, logger: logger); final scope = Scope(parentScope, logger: logger);
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 logger = MockLogger(); final logger = MockLogger();
final expectedIntValue = 5; final expectedIntValue = 5;
@@ -65,14 +173,12 @@ void main() {
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 logger = MockLogger(); final logger = MockLogger();
final parentScope = Scope(null, logger: logger); final parentScope = Scope(null, logger: logger);
final scope = Scope(parentScope, logger: logger); final scope = Scope(parentScope, logger: logger);
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 logger = MockLogger(); final logger = MockLogger();
final scope = Scope(null, logger: logger)..installModules([TestModule<int>(value: 5)]); final scope = Scope(null, logger: logger)..installModules([TestModule<int>(value: 5)]);
@@ -94,7 +200,6 @@ 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 logger = MockLogger(); final logger = MockLogger();
final scope = Scope(null, logger: logger) final scope = Scope(null, logger: logger)
@@ -104,7 +209,6 @@ void main() {
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 logger = MockLogger(); final logger = MockLogger();
final scope = Scope(null, logger: logger) final scope = Scope(null, logger: logger)
@@ -142,7 +246,6 @@ 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 logger = MockLogger(); final logger = MockLogger();
final scope = Scope(null, logger: logger) final scope = Scope(null, logger: logger)
@@ -153,7 +256,6 @@ 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 logger = MockLogger(); final logger = MockLogger();
final scope = Scope(null, logger: logger) final scope = Scope(null, logger: logger)
@@ -165,7 +267,6 @@ 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 logger = MockLogger(); final logger = MockLogger();
final scope = Scope(null, logger: logger); final scope = Scope(null, logger: logger);
@@ -181,42 +282,86 @@ void main() {
final scope = Scope(null, logger: logger); final scope = Scope(null, logger: logger);
expect(scope.tryResolve<int>(), isNull); expect(scope.tryResolve<int>(), isNull);
}); });
});
// Не реализован: // --------------------------------------------------------------------------
// test("Container bind() throws state error (if it's parent already has a resolver)", () { group('Disposable resource management', () {
// final parentScope = new Scope(null).installModules([TestModule<String>(value: "string one")]); test('scope.disposeAsync calls dispose on singleton disposable', () async {
// final scope = new Scope(parentScope); 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, logger: MockLogger());
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();
});
});
// expect( // --------------------------------------------------------------------------
// () => scope.installModules([TestModule<String>(value: "string two")]), // Расширенные edge-тесты для dispose и subScope
// throwsA(isA<StateError>())); 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);
await root.closeSubScope('feature');
expect(d.disposeCount, 1); // dispose должен быть вызван
// Повторное закрытие не вызывает double-dispose
await root.closeSubScope('feature');
expect(d.disposeCount, 1);
// Повторное открытие subScope создает NEW instance (dispose на старый не вызовется снова)
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);
});
}); });
} }
// ----------------------------------------------------------------------------
// Вспомогательные модули
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);
}
}
}
/// Вспомогательный модуль для подстановки builder'а через конструктор
class _InlineModule extends Module {
final void Function(Module, Scope) _builder;
_InlineModule(this._builder);
@override
void builder(Scope s) => _builder(this, s);
}

View File

@@ -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.

View File

@@ -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,

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -1,7 +1,7 @@
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

View File

@@ -1,3 +1,7 @@
## 1.1.3-dev.7
- **FIX**(license): correct urls.
## 1.1.3-dev.6 ## 1.1.3-dev.6
- Update a dependency to the latest release. - Update a dependency to the latest release.

View File

@@ -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,

View File

@@ -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).

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.6 version: 1.1.3-dev.7
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
@@ -19,7 +19,7 @@ environment:
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
cherrypick: ^3.0.0-dev.6 cherrypick: ^3.0.0-dev.7
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View File

@@ -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.

View File

@@ -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,

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -2,7 +2,7 @@ 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
@@ -18,7 +18,7 @@ environment:
# 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

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.
@@ -425,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

View File

@@ -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 через аннотации, что позволяет полностью избавиться от ручного внедрения зависимостей.
@@ -430,6 +465,15 @@ void main() {
--- ---
## 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)

View File

@@ -75,8 +75,54 @@ 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 ## Logging

View File

@@ -75,8 +75,54 @@ 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();
}
}
``` ```
## Логирование ## Логирование

View File

@@ -127,7 +127,7 @@ packages:
path: "../../cherrypick" path: "../../cherrypick"
relative: true relative: true
source: path source: path
version: "3.0.0-dev.3" version: "3.0.0-dev.5"
cherrypick_annotations: cherrypick_annotations:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -141,7 +141,7 @@ packages:
path: "../../cherrypick_flutter" path: "../../cherrypick_flutter"
relative: true relative: true
source: path source: path
version: "1.1.3-dev.3" version: "1.1.3-dev.5"
cherrypick_generator: cherrypick_generator:
dependency: "direct dev" dependency: "direct dev"
description: description:

View File

@@ -151,7 +151,7 @@ packages:
path: "../../cherrypick" path: "../../cherrypick"
relative: true relative: true
source: path source: path
version: "3.0.0-dev.3" version: "3.0.0-dev.5"
cherrypick_annotations: cherrypick_annotations:
dependency: "direct main" dependency: "direct main"
description: description: