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.
This commit is contained in:
Sergey Penkovsky
2025-08-01 15:07:12 +03:00
parent 61f2268d63
commit a9c95f6a89
14 changed files with 516 additions and 69 deletions

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
CherryPick supports DI with annotations, letting you eliminate manual DI setup.

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 через аннотации, что позволяет полностью избавиться от ручного внедрения зависимостей.

View File

@@ -75,8 +75,54 @@ Example:
// or
final str = rootScope.tryResolve<String>();
// close main scope
Cherrypick.closeRootScope();
// Recommended: Close the root scope & automatically release all Disposable resources
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

View File

@@ -75,8 +75,54 @@ Scope - это контейнер, который хранит все дерев
// или
final str = rootScope.tryResolve<String>();
// закрыть главный scope
Cherrypick.closeRootScope();
// Рекомендуется: закрывайте главный scope для автоматического освобождения всех ресурсов
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();
}
}
```
## Логирование