mirror of
https://github.com/pese-git/cherrypick.git
synced 2026-01-23 21:13:35 +00:00
Compare commits
32 Commits
cherrypick
...
cherrypick
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
900cd68663 | ||
|
|
57e4196b95 | ||
|
|
358da8f96b | ||
|
|
ea2b6687f4 | ||
|
|
df00a2a5d2 | ||
|
|
d5983a4a0b | ||
|
|
125bccfa5a | ||
|
|
12b97c9368 | ||
|
|
424aaa3e22 | ||
|
|
2ec3a86a2f | ||
|
|
efed72cc39 | ||
|
|
4dc9e269cd | ||
|
|
d153ab4255 | ||
|
|
6924ccd07b | ||
|
|
26b843f791 | ||
|
|
8eafba4e4b | ||
|
|
ad6e9bbc3d | ||
|
|
bea8affcab | ||
|
|
1d7b9a9166 | ||
|
|
016c212063 | ||
|
|
d93d4173a2 | ||
|
|
85aa23d7ed | ||
|
|
51cf4a0dc0 | ||
|
|
8f980ff111 | ||
|
|
4d872d7c25 | ||
|
|
450f4231cb | ||
|
|
cd1b9cf49d | ||
|
|
33775f5748 | ||
|
|
e5848784ac | ||
|
|
a4b0ddfa54 | ||
|
|
547a15fa4e | ||
|
|
a9c95f6a89 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -18,5 +18,7 @@ pubspec_overrides.yaml
|
|||||||
melos_cherrypick.iml
|
melos_cherrypick.iml
|
||||||
melos_cherrypick_workspace.iml
|
melos_cherrypick_workspace.iml
|
||||||
melos_cherrypick_flutter.iml
|
melos_cherrypick_flutter.iml
|
||||||
|
melos_benchmark_di.iml
|
||||||
|
melos_talker_cherrypick_logger.iml
|
||||||
|
|
||||||
coverage
|
coverage
|
||||||
78
CHANGELOG.md
78
CHANGELOG.md
@@ -3,6 +3,84 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## 2025-08-12
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Packages with breaking changes:
|
||||||
|
|
||||||
|
- There are no breaking changes in this release.
|
||||||
|
|
||||||
|
Packages with other changes:
|
||||||
|
|
||||||
|
- [`cherrypick` - `v3.0.0-dev.8`](#cherrypick---v300-dev8)
|
||||||
|
- [`cherrypick_flutter` - `v1.1.3-dev.8`](#cherrypick_flutter---v113-dev8)
|
||||||
|
|
||||||
|
Packages with dependency updates only:
|
||||||
|
|
||||||
|
> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project.
|
||||||
|
|
||||||
|
- `cherrypick_flutter` - `v1.1.3-dev.8`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `cherrypick` - `v3.0.0-dev.8`
|
||||||
|
|
||||||
|
- **REFACTOR**(tests): replace MockLogger with MockObserver in scope tests to align with updated observer API.
|
||||||
|
- **FIX**(doc): remove hide symbol.
|
||||||
|
- **FEAT**(core): add full DI lifecycle observability via onInstanceDisposed.
|
||||||
|
- **DOCS**(logging): update Logging section in README with modern Observer usage and Talker integration examples.
|
||||||
|
- **DOCS**(observer): improve documentation, translate all comments to English, add usage examples.
|
||||||
|
- **DOCS**(README): add section with overview table for additional modules.
|
||||||
|
- **DOCS**(README): refactor structure and improve clarity of advanced features.
|
||||||
|
- **DOCS**(README): add 'Hierarchical Subscopes' section and update structure for advanced features clarity.
|
||||||
|
|
||||||
|
|
||||||
|
## 2025-08-11
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Packages with breaking changes:
|
||||||
|
|
||||||
|
- [`cherrypick` - `v3.0.0-dev.7`](#cherrypick---v300-dev7)
|
||||||
|
|
||||||
|
Packages with other changes:
|
||||||
|
|
||||||
|
- [`cherrypick_annotations` - `v1.1.1`](#cherrypick_annotations---v111)
|
||||||
|
- [`cherrypick_flutter` - `v1.1.3-dev.7`](#cherrypick_flutter---v113-dev7)
|
||||||
|
- [`cherrypick_generator` - `v1.1.1`](#cherrypick_generator---v111)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `cherrypick` - `v3.0.0-dev.7`
|
||||||
|
|
||||||
|
- **FIX**(comment): fix warnings.
|
||||||
|
- **FIX**(license): correct urls.
|
||||||
|
- **FEAT**: add Disposable interface source and usage example.
|
||||||
|
- **DOCS**(readme): add comprehensive section on annotations and DI code generation.
|
||||||
|
- **DOCS**(readme): add detailed section and examples for automatic Disposable resource cleanup\n\n- Added a dedicated section with English description and code samples on using Disposable for automatic resource management.\n- Updated Features to include automatic resource cleanup for Disposable dependencies.\n\nHelps developers understand and implement robust DI resource management practices.
|
||||||
|
- **DOCS**(faq): add best practice FAQ about using await with scope disposal.
|
||||||
|
- **DOCS**(faq): add best practice FAQ about using await with scope disposal.
|
||||||
|
- **BREAKING** **REFACTOR**(core): make closeRootScope async and await dispose.
|
||||||
|
- **BREAKING** **DOCS**(disposable): add detailed English documentation and usage examples for Disposable interface; chore: update binding_resolver and add explanatory comment in scope_test for deprecated usage.\n\n- Expanded Disposable interface docs, added sync & async example classes, and CherryPick integration sample.\n- Clarified how to implement and use Disposable in DI context.\n- Updated binding_resolver for internal improvements.\n- Added ignore for deprecated member use in scope_test for clarity and future upgrades.\n\nBREAKING CHANGE: Documentation style enhancement and clearer API usage for Disposable implementations.
|
||||||
|
|
||||||
|
#### `cherrypick_annotations` - `v1.1.1`
|
||||||
|
|
||||||
|
- **FIX**(license): correct urls.
|
||||||
|
|
||||||
|
#### `cherrypick_flutter` - `v1.1.3-dev.7`
|
||||||
|
|
||||||
|
- **FIX**(license): correct urls.
|
||||||
|
|
||||||
|
#### `cherrypick_generator` - `v1.1.1`
|
||||||
|
|
||||||
|
- **FIX**(license): correct urls.
|
||||||
|
|
||||||
|
|
||||||
## 2025-08-08
|
## 2025-08-08
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -192,7 +192,7 @@
|
|||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
|||||||
@@ -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.7"
|
||||||
collection:
|
collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -1,3 +1,28 @@
|
|||||||
|
## 3.0.0-dev.8
|
||||||
|
|
||||||
|
- **REFACTOR**(tests): replace MockLogger with MockObserver in scope tests to align with updated observer API.
|
||||||
|
- **FIX**(doc): remove hide symbol.
|
||||||
|
- **FEAT**(core): add full DI lifecycle observability via onInstanceDisposed.
|
||||||
|
- **DOCS**(logging): update Logging section in README with modern Observer usage and Talker integration examples.
|
||||||
|
- **DOCS**(observer): improve documentation, translate all comments to English, add usage examples.
|
||||||
|
- **DOCS**(README): add section with overview table for additional modules.
|
||||||
|
- **DOCS**(README): refactor structure and improve clarity of advanced features.
|
||||||
|
- **DOCS**(README): add 'Hierarchical Subscopes' section and update structure for advanced features clarity.
|
||||||
|
|
||||||
|
## 3.0.0-dev.7
|
||||||
|
|
||||||
|
> Note: This release has breaking changes.
|
||||||
|
|
||||||
|
- **FIX**(comment): fix warnings.
|
||||||
|
- **FIX**(license): correct urls.
|
||||||
|
- **FEAT**: add Disposable interface source and usage example.
|
||||||
|
- **DOCS**(readme): add comprehensive section on annotations and DI code generation.
|
||||||
|
- **DOCS**(readme): add detailed section and examples for automatic Disposable resource cleanup\n\n- Added a dedicated section with English description and code samples on using Disposable for automatic resource management.\n- Updated Features to include automatic resource cleanup for Disposable dependencies.\n\nHelps developers understand and implement robust DI resource management practices.
|
||||||
|
- **DOCS**(faq): add best practice FAQ about using await with scope disposal.
|
||||||
|
- **DOCS**(faq): add best practice FAQ about using await with scope disposal.
|
||||||
|
- **BREAKING** **REFACTOR**(core): make closeRootScope async and await dispose.
|
||||||
|
- **BREAKING** **DOCS**(disposable): add detailed English documentation and usage examples for Disposable interface; chore: update binding_resolver and add explanatory comment in scope_test for deprecated usage.\n\n- Expanded Disposable interface docs, added sync & async example classes, and CherryPick integration sample.\n- Clarified how to implement and use Disposable in DI context.\n- Updated binding_resolver for internal improvements.\n- Added ignore for deprecated member use in scope_test for clarity and future upgrades.\n\nBREAKING CHANGE: Documentation style enhancement and clearer API usage for Disposable implementations.
|
||||||
|
|
||||||
## 3.0.0-dev.6
|
## 3.0.0-dev.6
|
||||||
|
|
||||||
> Note: This release has breaking changes.
|
> Note: This release has breaking changes.
|
||||||
|
|||||||
@@ -192,7 +192,7 @@
|
|||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
|||||||
@@ -1,18 +1,103 @@
|
|||||||
# CherryPick
|
# CherryPick
|
||||||
|
|
||||||
`cherrypick` is a flexible and lightweight dependency injection library for Dart and Flutter. It provides an easy-to-use system for registering, scoping, and resolving dependencies using modular bindings and hierarchical scopes. The design enables cleaner architecture, testability, and modular code in your applications.
|
`cherrypick` is a flexible and lightweight dependency injection library for Dart and Flutter.
|
||||||
|
It provides an easy-to-use system for registering, scoping, and resolving dependencies using modular bindings and hierarchical scopes. The design enables cleaner architecture, testability, and modular code in your applications.
|
||||||
|
|
||||||
## Key Concepts
|
---
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
- [Key Features](#key-features)
|
||||||
|
- [Installation](#installation)
|
||||||
|
- [Getting Started](#getting-started)
|
||||||
|
- [Core Concepts](#core-concepts)
|
||||||
|
- [Binding](#binding)
|
||||||
|
- [Module](#module)
|
||||||
|
- [Scope](#scope)
|
||||||
|
- [Disposable](#disposable)
|
||||||
|
- [Dependency Resolution API](#dependency-resolution-api)
|
||||||
|
- [Using Annotations & Code Generation](#using-annotations--code-generation)
|
||||||
|
- [Advanced Features](#advanced-features)
|
||||||
|
- [Hierarchical Subscopes](#hierarchical-subscopes)
|
||||||
|
- [Logging](#logging)
|
||||||
|
- [Circular Dependency Detection](#circular-dependency-detection)
|
||||||
|
- [Performance Improvements](#performance-improvements)
|
||||||
|
- [Example Application](#example-application)
|
||||||
|
- [FAQ](#faq)
|
||||||
|
- [Documentation Links](#documentation-links)
|
||||||
|
- [Additional Modules](#additional-modules)
|
||||||
|
- [Contributing](#contributing)
|
||||||
|
- [License](#license)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Features
|
||||||
|
- Main Scope and Named Subscopes
|
||||||
|
- Named Instance Binding and Resolution
|
||||||
|
- Asynchronous and Synchronous Providers
|
||||||
|
- Providers Supporting Runtime Parameters
|
||||||
|
- Singleton Lifecycle Management
|
||||||
|
- Modular and Hierarchical Composition
|
||||||
|
- Null-safe Resolution (tryResolve/tryResolveAsync)
|
||||||
|
- Circular Dependency Detection (Local and Global)
|
||||||
|
- Comprehensive logging of dependency injection state and actions
|
||||||
|
- Automatic resource cleanup for all registered Disposable dependencies
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Add to your `pubspec.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
dependencies:
|
||||||
|
cherrypick: ^<latest_version>
|
||||||
|
````
|
||||||
|
|
||||||
|
Then run:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
dart pub get
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
Here is a minimal example that registers and resolves a dependency:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
|
||||||
|
class AppModule extends Module {
|
||||||
|
@override
|
||||||
|
void builder(Scope currentScope) {
|
||||||
|
bind<ApiClient>().toInstance(ApiClientMock());
|
||||||
|
bind<String>().toProvide(() => "Hello, CherryPick!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final rootScope = CherryPick.openRootScope();
|
||||||
|
rootScope.installModules([AppModule()]);
|
||||||
|
|
||||||
|
final greeting = rootScope.resolve<String>();
|
||||||
|
print(greeting); // prints: Hello, CherryPick!
|
||||||
|
|
||||||
|
await CherryPick.closeRootScope();
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Core Concepts
|
||||||
|
|
||||||
### Binding
|
### Binding
|
||||||
|
|
||||||
A **Binding** acts as a configuration for how to create or provide a particular dependency. Bindings support:
|
A **Binding** acts as a configuration for how to create or provide a particular dependency. Bindings support:
|
||||||
|
|
||||||
- Direct instance assignment (`toInstance()`, `toInstanceAsync()`)
|
* Direct instance assignment (`toInstance()`, `toInstanceAsync()`)
|
||||||
- Lazy providers (sync/async functions)
|
* Lazy providers (sync/async functions)
|
||||||
- Provider functions supporting dynamic parameters
|
* Provider functions supporting dynamic parameters
|
||||||
- Named instances for resolving by string key
|
* Named instances for resolving by string key
|
||||||
- Optional singleton lifecycle
|
* Optional singleton lifecycle
|
||||||
|
|
||||||
#### Example
|
#### Example
|
||||||
|
|
||||||
@@ -79,29 +164,67 @@ final str = rootScope.resolve<String>();
|
|||||||
// Resolve a dependency asynchronously
|
// Resolve a dependency asynchronously
|
||||||
final result = await rootScope.resolveAsync<String>();
|
final result = await rootScope.resolveAsync<String>();
|
||||||
|
|
||||||
// Close the root scope once done
|
// Recommended: Close the root scope and release all resources
|
||||||
CherryPick.closeRootScope();
|
await CherryPick.closeRootScope();
|
||||||
|
|
||||||
|
// Alternatively, you may manually call dispose on any scope you manage individually
|
||||||
|
// await rootScope.dispose();
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Working with Subscopes
|
---
|
||||||
|
|
||||||
|
### Disposable
|
||||||
|
|
||||||
|
CherryPick can automatically clean up any dependency that implements the `Disposable` interface. This makes resource management (for controllers, streams, sockets, files, etc.) easy and reliable—especially when scopes or the app are shut down.
|
||||||
|
|
||||||
|
If you bind an object implementing `Disposable` as a singleton or provide it via the DI container, CherryPick will call its `dispose()` method when the scope is closed or cleaned up.
|
||||||
|
|
||||||
|
#### Key Points
|
||||||
|
- Supports both synchronous and asynchronous cleanup (dispose may return `void` or `Future`).
|
||||||
|
- All `Disposable` instances from the current scope and subscopes will be disposed in the correct order.
|
||||||
|
- Prevents resource leaks and enforces robust cleanup.
|
||||||
|
- No manual wiring needed once your class implements `Disposable`.
|
||||||
|
|
||||||
|
#### Minimal Sync Example
|
||||||
```dart
|
```dart
|
||||||
// Open a named child scope (e.g., for a feature/module)
|
class CacheManager implements Disposable {
|
||||||
final subScope = rootScope.openSubScope('featureScope')
|
void dispose() {
|
||||||
..installModules([FeatureModule()]);
|
cache.clear();
|
||||||
|
print('CacheManager disposed!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Resolve from subScope, with fallback to parents if missing
|
final scope = CherryPick.openRootScope();
|
||||||
final dataBloc = await subScope.resolveAsync<DataBloc>();
|
scope.installModules([
|
||||||
|
Module((bind) => bind<CacheManager>().toProvide(() => CacheManager()).singleton()),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// ...later
|
||||||
|
await CherryPick.closeRootScope(); // prints: CacheManager disposed!
|
||||||
```
|
```
|
||||||
|
|
||||||
### Fast Dependency Lookup (Performance Improvement)
|
#### Async Example
|
||||||
|
```dart
|
||||||
|
class MyServiceWithSocket implements Disposable {
|
||||||
|
@override
|
||||||
|
Future<void> dispose() async {
|
||||||
|
await socket.close();
|
||||||
|
print('Socket closed!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
> **Performance Note:**
|
scope.installModules([
|
||||||
> **Starting from version 3.0.0**, CherryPick uses a Map-based resolver index for dependency lookup. This means calls to `resolve<T>()` and related methods are now O(1) operations, regardless of the number of modules or bindings in your scope. Previously, the library had to iterate over all modules and bindings to locate the requested dependency, which could impact performance as your project grew.
|
Module((bind) => bind<MyServiceWithSocket>().toProvide(() => MyServiceWithSocket()).singleton()),
|
||||||
>
|
]);
|
||||||
> This optimization is internal and does not change any library APIs or usage patterns, but it significantly improves resolution speed in larger applications.
|
|
||||||
|
|
||||||
### Dependency Lookup API
|
await CherryPick.closeRootScope(); // awaits async disposal
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tip:** Always call `await CherryPick.closeRootScope()` or `await scope.closeSubScope(key)` in your shutdown/teardown logic to ensure all resources are released automatically.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dependency Resolution API
|
||||||
|
|
||||||
- `resolve<T>()` — Locates a dependency instance or throws if missing.
|
- `resolve<T>()` — Locates a dependency instance or throws if missing.
|
||||||
- `resolveAsync<T>()` — Async variant for dependencies requiring async binding.
|
- `resolveAsync<T>()` — Async variant for dependencies requiring async binding.
|
||||||
@@ -113,6 +236,335 @@ Supports:
|
|||||||
- Named dependencies
|
- Named dependencies
|
||||||
- Provider functions with and without runtime parameters
|
- Provider functions with and without runtime parameters
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Using Annotations & Code Generation
|
||||||
|
|
||||||
|
CherryPick provides best-in-class developer ergonomics and type safety through **Dart annotations** and code generation. This lets you dramatically reduce boilerplate: simply annotate your classes, fields, and modules, run the code generator, and enjoy auto-wired dependency injection!
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
|
||||||
|
1. **Annotate** your services, providers, and fields using `cherrypick_annotations`.
|
||||||
|
2. **Generate** code using `cherrypick_generator` with `build_runner`.
|
||||||
|
3. **Use** generated modules and mixins for fully automated DI (dependency injection).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Supported Annotations
|
||||||
|
|
||||||
|
| Annotation | Target | Description |
|
||||||
|
|-------------------|---------------|--------------------------------------------------------------------------------|
|
||||||
|
| `@injectable()` | class | Enables automatic field injection for this class (mixin will be generated) |
|
||||||
|
| `@inject()` | field | Field will be injected using DI (works with @injectable classes) |
|
||||||
|
| `@module()` | class | Declares a DI module; its methods can provide services/providers |
|
||||||
|
| `@provide` | method | Registers as a DI provider method (may have dependencies as parameters) |
|
||||||
|
| `@instance` | method/class | Registers an instance (new object on each resolution, i.e. factory) |
|
||||||
|
| `@singleton` | method/class | Registers as a singleton (one instance per scope) |
|
||||||
|
| `@named` | field/param | Use named instance (bind/resolve by name or apply to field/param) |
|
||||||
|
| `@scope` | field/param | Inject or resolve from a specific named scope |
|
||||||
|
| `@params` | param | Marks method parameter as filled by user-supplied runtime params at resolution |
|
||||||
|
|
||||||
|
You can easily **combine** these annotations for advanced scenarios!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Field Injection Example
|
||||||
|
|
||||||
|
```dart
|
||||||
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
class ProfilePage with _\$ProfilePage {
|
||||||
|
@inject()
|
||||||
|
late final AuthService auth;
|
||||||
|
|
||||||
|
@inject()
|
||||||
|
@scope('profile')
|
||||||
|
late final ProfileManager manager;
|
||||||
|
|
||||||
|
@inject()
|
||||||
|
@named('admin')
|
||||||
|
late final UserService adminUserService;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- After running build_runner, the mixin `_ProfilePage` will be generated for field injection.
|
||||||
|
- Call `myProfilePage.injectFields();` or use the mixin's auto-inject feature, and all dependencies will be set up for you.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Module and Provider Example
|
||||||
|
|
||||||
|
```dart
|
||||||
|
@module()
|
||||||
|
abstract class AppModule {
|
||||||
|
@singleton
|
||||||
|
AuthService provideAuth(Api api) => AuthService(api);
|
||||||
|
|
||||||
|
@named('logging')
|
||||||
|
@provide
|
||||||
|
Future<Logger> provideLogger(@params Map<String, dynamic> args) async => ...;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Mark class as `@module`, write provider methods.
|
||||||
|
- Use `@singleton`, `@named`, `@provide`, `@params` to control lifecycle, key names, and parameters.
|
||||||
|
- The generator will produce a class like `$AppModule` with the proper DI bindings.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Usage Steps
|
||||||
|
|
||||||
|
1. **Add to your pubspec.yaml**:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
dependencies:
|
||||||
|
cherrypick: any
|
||||||
|
cherrypick_annotations: any
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
cherrypick_generator: any
|
||||||
|
build_runner: any
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Annotate** your classes and modules as above.
|
||||||
|
|
||||||
|
3. **Run code generation:**
|
||||||
|
|
||||||
|
```shell
|
||||||
|
dart run build_runner build --delete-conflicting-outputs
|
||||||
|
# or in Flutter:
|
||||||
|
flutter pub run build_runner build --delete-conflicting-outputs
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Register modules and use auto-injection:**
|
||||||
|
|
||||||
|
```dart
|
||||||
|
final scope = CherryPick.openRootScope()
|
||||||
|
..installModules([$AppModule()]);
|
||||||
|
|
||||||
|
final profile = ProfilePage();
|
||||||
|
profile.injectFields(); // injects all @inject fields
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Advanced: Parameters, Named Instances, and Scopes
|
||||||
|
|
||||||
|
- Use `@named` for key-based multi-implementation injection.
|
||||||
|
- Use `@scope` when dependencies live in a non-root scope.
|
||||||
|
- Use `@params` for runtime arguments passed during resolution.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Troubleshooting & Tips
|
||||||
|
|
||||||
|
- After modifying DI-related code, always re-run `build_runner`.
|
||||||
|
- Do not manually edit `.g.dart` files—let the generator manage them.
|
||||||
|
- Errors in annotation usage (e.g., using `@singleton` on wrong target) are shown at build time.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### References
|
||||||
|
|
||||||
|
- [Full annotation reference (en)](doc/annotations_en.md)
|
||||||
|
- [cherrypick_annotations/README.md](../cherrypick_annotations/README.md)
|
||||||
|
- [cherrypick_generator/README.md](../cherrypick_generator/README.md)
|
||||||
|
- See the [`examples/postly`](../examples/postly) for a full working DI+annotations app.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Advanced Features
|
||||||
|
|
||||||
|
### Hierarchical Subscopes
|
||||||
|
|
||||||
|
CherryPick supports a hierarchical structure of scopes, allowing you to create complex and modular dependency graphs for advanced application architectures. Each subscope inherits from its parent, enabling context-specific overrides while still allowing access to global or shared services.
|
||||||
|
|
||||||
|
#### Key Points
|
||||||
|
|
||||||
|
- **Subscopes** are child scopes that can be opened from any existing scope (including the root).
|
||||||
|
- Dependencies registered in a subscope override those from parent scopes when resolved.
|
||||||
|
- If a dependency is not found in the current subscope, the resolution process automatically searches parent scopes up the hierarchy.
|
||||||
|
- Subscopes can have their own modules, lifetime, and disposable objects.
|
||||||
|
- You can nest subscopes to any depth, modeling features, flows, or components independently.
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
```dart
|
||||||
|
final rootScope = CherryPick.openRootScope();
|
||||||
|
rootScope.installModules([AppModule()]);
|
||||||
|
|
||||||
|
// Open a hierarchical subscope for a feature or page
|
||||||
|
final userFeatureScope = rootScope.openSubScope('userFeature');
|
||||||
|
userFeatureScope.installModules([UserFeatureModule()]);
|
||||||
|
|
||||||
|
// Dependencies defined in UserFeatureModule will take precedence
|
||||||
|
final userService = userFeatureScope.resolve<UserService>();
|
||||||
|
|
||||||
|
// If not found in the subscope, lookup continues in the parent (rootScope)
|
||||||
|
final sharedService = userFeatureScope.resolve<SharedService>();
|
||||||
|
|
||||||
|
// You can nest subscopes
|
||||||
|
final dialogScope = userFeatureScope.openSubScope('dialog');
|
||||||
|
dialogScope.installModules([DialogModule()]);
|
||||||
|
final dialogManager = dialogScope.resolve<DialogManager>();
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Use Cases
|
||||||
|
|
||||||
|
- Isolate feature modules, flows, or screens with their own dependencies.
|
||||||
|
- Provide and override services for specific navigation stacks or platform-specific branches.
|
||||||
|
- Manage the lifetime and disposal of groups of dependencies independently (e.g., per-user, per-session, per-component).
|
||||||
|
|
||||||
|
**Tip:** Always close subscopes when they are no longer needed to release resources and trigger cleanup of Disposable dependencies.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Logging
|
||||||
|
|
||||||
|
CherryPick lets you log all dependency injection (DI) events and errors using a flexible observer mechanism.
|
||||||
|
|
||||||
|
#### Custom Observers
|
||||||
|
|
||||||
|
You can pass any implementation of `CherryPickObserver` to your root scope or any sub-scope.
|
||||||
|
This allows centralized and extensible logging, which you can direct to print, files, visualization frameworks, external loggers, or systems like [Talker](https://pub.dev/packages/talker).
|
||||||
|
|
||||||
|
##### Example: Printing All Events
|
||||||
|
|
||||||
|
```dart
|
||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// Use the built-in PrintCherryPickObserver for console logs
|
||||||
|
final observer = PrintCherryPickObserver();
|
||||||
|
final scope = CherryPick.openRootScope(observer: observer);
|
||||||
|
// All DI actions and errors will now be printed!
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Example: Advanced Logging with Talker
|
||||||
|
|
||||||
|
For richer logging, analytics, or UI overlays, use an advanced observer such as [talker_cherrypick_logger](../talker_cherrypick_logger):
|
||||||
|
|
||||||
|
```dart
|
||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
import 'package:talker/talker.dart';
|
||||||
|
import 'package:talker_cherrypick_logger/talker_cherrypick_logger.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
final talker = Talker();
|
||||||
|
final observer = TalkerCherryPickObserver(talker);
|
||||||
|
CherryPick.openRootScope(observer: observer);
|
||||||
|
// All container events go to the Talker log system!
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Default Behavior
|
||||||
|
|
||||||
|
- By default, logging is silent (`SilentCherryPickObserver`) for production, with no output unless you supply an observer.
|
||||||
|
- You can configure observers **per scope** for isolated, test-specific, or feature-specific logging.
|
||||||
|
|
||||||
|
#### Observer Capabilities
|
||||||
|
|
||||||
|
Events you can observe and log:
|
||||||
|
- Dependency registration
|
||||||
|
- Instance requests, creations, disposals
|
||||||
|
- Module installs/removals
|
||||||
|
- Scope opening/closing
|
||||||
|
- Cache hits/misses
|
||||||
|
- Cycle detection
|
||||||
|
- Diagnostics, warnings, errors
|
||||||
|
|
||||||
|
Just implement or extend `CherryPickObserver` and direct messages anywhere you want!
|
||||||
|
|
||||||
|
#### When to Use
|
||||||
|
|
||||||
|
- Enable verbose logging and debugging in development or test builds.
|
||||||
|
- Route logs to your main log system or analytics.
|
||||||
|
- Hook into DI lifecycle for profiling or monitoring.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Circular Dependency Detection
|
||||||
|
|
||||||
|
CherryPick can detect circular dependencies in your DI configuration, helping you avoid infinite loops and hard-to-debug errors.
|
||||||
|
|
||||||
|
**How to use:**
|
||||||
|
|
||||||
|
#### 1. Enable Cycle Detection for Development
|
||||||
|
|
||||||
|
**Local detection (within one scope):**
|
||||||
|
```dart
|
||||||
|
final scope = CherryPick.openSafeRootScope(); // Local detection enabled by default
|
||||||
|
// or, for an existing scope:
|
||||||
|
scope.enableCycleDetection();
|
||||||
|
```
|
||||||
|
|
||||||
|
**Global detection (across all scopes):**
|
||||||
|
```dart
|
||||||
|
CherryPick.enableGlobalCrossScopeCycleDetection();
|
||||||
|
final rootScope = CherryPick.openGlobalSafeRootScope();
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. Error Example
|
||||||
|
|
||||||
|
If you declare mutually dependent services:
|
||||||
|
```dart
|
||||||
|
class A { A(B b); }
|
||||||
|
class B { B(A a); }
|
||||||
|
|
||||||
|
scope.installModules([
|
||||||
|
Module((bind) {
|
||||||
|
bind<A>().to((s) => A(s.resolve<B>()));
|
||||||
|
bind<B>().to((s) => B(s.resolve<A>()));
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
scope.resolve<A>(); // Throws CircularDependencyException
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. Typical Usage Pattern
|
||||||
|
|
||||||
|
- **Always enable detection** in debug and test environments for maximum safety.
|
||||||
|
- **Disable detection** in production for performance (after code is tested).
|
||||||
|
|
||||||
|
```dart
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
if (kDebugMode) {
|
||||||
|
CherryPick.enableGlobalCycleDetection();
|
||||||
|
CherryPick.enableGlobalCrossScopeCycleDetection();
|
||||||
|
}
|
||||||
|
runApp(MyApp());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4. Handling and Debugging Errors
|
||||||
|
|
||||||
|
On detection, `CircularDependencyException` is thrown with a readable dependency chain:
|
||||||
|
```dart
|
||||||
|
try {
|
||||||
|
scope.resolve<MyService>();
|
||||||
|
} on CircularDependencyException catch (e) {
|
||||||
|
print('Dependency chain: ${e.dependencyChain}');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**More details:** See [cycle_detection.en.md](doc/cycle_detection.en.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
> **Performance Note:**
|
||||||
|
> **Starting from version 3.0.0**, CherryPick uses a Map-based resolver index for dependency lookup. This means calls to `resolve<T>()` and related methods are now O(1) operations, regardless of the number of modules or bindings in your scope. Previously, the library had to iterate over all modules and bindings to locate the requested dependency, which could impact performance as your project grew.
|
||||||
|
>
|
||||||
|
> This optimization is internal and does not change any library APIs or usage patterns, but it significantly improves resolution speed in larger applications.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Example Application
|
## Example Application
|
||||||
|
|
||||||
Below is a complete example illustrating modules, subscopes, async providers, and dependency resolution.
|
Below is a complete example illustrating modules, subscopes, async providers, and dependency resolution.
|
||||||
@@ -234,129 +686,51 @@ class ApiClientImpl implements ApiClient {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Logging
|
---
|
||||||
|
|
||||||
CherryPick supports centralized logging of all dependency injection (DI) events and errors. You can globally enable logs for your application or test environment with:
|
## FAQ
|
||||||
|
|
||||||
```dart
|
### Q: Do I need to use `await` with CherryPick.closeRootScope(), CherryPick.closeScope(), or scope.dispose() if I have no Disposable services?
|
||||||
import 'package:cherrypick/cherrypick.dart';
|
|
||||||
|
|
||||||
void main() {
|
**A:**
|
||||||
// Set a global logger before any scopes are created
|
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.
|
||||||
CherryPick.setGlobalLogger(PrintLogger()); // or your custom logger
|
|
||||||
|
|
||||||
final scope = CherryPick.openRootScope();
|
---
|
||||||
// All DI actions and errors will now be logged!
|
|
||||||
}
|
|
||||||
```
|
|
||||||
- All dependency resolution, scope creation, module installation, and circular dependency errors will be sent to your logger (via info/error method).
|
|
||||||
- By default, logs are off (SilentLogger is used in production).
|
|
||||||
|
|
||||||
If you want fine-grained, test-local, or isolated logging, you can provide a logger directly to each scope:
|
## Documentation Links
|
||||||
|
|
||||||
```dart
|
* Circular Dependency Detection [(En)](doc/cycle_detection.en.md)[(Ru)](doc/cycle_detection.ru.md)
|
||||||
final logger = MockLogger();
|
|
||||||
final scope = Scope(null, logger: logger); // works in tests for isolation
|
|
||||||
scope.installModules([...]);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Features
|
---
|
||||||
|
|
||||||
- [x] Main Scope and Named Subscopes
|
## Additional Modules
|
||||||
- [x] Named Instance Binding and Resolution
|
|
||||||
- [x] Asynchronous and Synchronous Providers
|
|
||||||
- [x] Providers Supporting Runtime Parameters
|
|
||||||
- [x] Singleton Lifecycle Management
|
|
||||||
- [x] Modular and Hierarchical Composition
|
|
||||||
- [x] Null-safe Resolution (tryResolve/tryResolveAsync)
|
|
||||||
- [x] Circular Dependency Detection (Local and Global)
|
|
||||||
- [x] Comprehensive logging of dependency injection state and actions
|
|
||||||
|
|
||||||
## Quick Guide: Circular Dependency Detection
|
CherryPick provides a set of official add-on modules for advanced use cases and specific platforms:
|
||||||
|
|
||||||
CherryPick can detect circular dependencies in your DI configuration, helping you avoid infinite loops and hard-to-debug errors.
|
| Module name | Description | Documentation |
|
||||||
|
|-------------|-------------|---------------|
|
||||||
|
| [**cherrypick_annotations**](https://pub.dev/packages/cherrypick_annotations) | Dart annotations for concise DI definitions and code generation. | [README](../cherrypick_annotations/README.md) |
|
||||||
|
| [**cherrypick_generator**](https://pub.dev/packages/cherrypick_generator) | Code generator to produce DI bindings based on annotations. | [README](../cherrypick_generator/README.md) |
|
||||||
|
| [**cherrypick_flutter**](https://pub.dev/packages/cherrypick_flutter) | Flutter integration: DI provider widgets and helpers for Flutter. | [README](../cherrypick_flutter/README.md) |
|
||||||
|
|
||||||
**How to use:**
|
---
|
||||||
|
|
||||||
### 1. Enable Cycle Detection for Development
|
|
||||||
|
|
||||||
**Local detection (within one scope):**
|
|
||||||
```dart
|
|
||||||
final scope = CherryPick.openSafeRootScope(); // Local detection enabled by default
|
|
||||||
// or, for an existing scope:
|
|
||||||
scope.enableCycleDetection();
|
|
||||||
```
|
|
||||||
|
|
||||||
**Global detection (across all scopes):**
|
|
||||||
```dart
|
|
||||||
CherryPick.enableGlobalCrossScopeCycleDetection();
|
|
||||||
final rootScope = CherryPick.openGlobalSafeRootScope();
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Error Example
|
|
||||||
|
|
||||||
If you declare mutually dependent services:
|
|
||||||
```dart
|
|
||||||
class A { A(B b); }
|
|
||||||
class B { B(A a); }
|
|
||||||
|
|
||||||
scope.installModules([
|
|
||||||
Module((bind) {
|
|
||||||
bind<A>().to((s) => A(s.resolve<B>()));
|
|
||||||
bind<B>().to((s) => B(s.resolve<A>()));
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
scope.resolve<A>(); // Throws CircularDependencyException
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Typical Usage Pattern
|
|
||||||
|
|
||||||
- **Always enable detection** in debug and test environments for maximum safety.
|
|
||||||
- **Disable detection** in production for performance (after code is tested).
|
|
||||||
|
|
||||||
```dart
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
if (kDebugMode) {
|
|
||||||
CherryPick.enableGlobalCycleDetection();
|
|
||||||
CherryPick.enableGlobalCrossScopeCycleDetection();
|
|
||||||
}
|
|
||||||
runApp(MyApp());
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Handling and Debugging Errors
|
|
||||||
|
|
||||||
On detection, `CircularDependencyException` is thrown with a readable dependency chain:
|
|
||||||
```dart
|
|
||||||
try {
|
|
||||||
scope.resolve<MyService>();
|
|
||||||
} on CircularDependencyException catch (e) {
|
|
||||||
print('Dependency chain: ${e.dependencyChain}');
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**More details:** See [cycle_detection.en.md](doc/cycle_detection.en.md)
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
- [Circular Dependency Detection (English)](doc/cycle_detection.en.md)
|
|
||||||
- [Обнаружение циклических зависимостей (Русский)](doc/cycle_detection.ru.md)
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Contributions are welcome! Please open issues or submit pull requests on [GitHub](https://github.com/pese-git/cherrypick).
|
Contributions are welcome! Please open issues or submit pull requests on [GitHub](https://github.com/pese-git/cherrypick).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Licensed under the [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0).
|
Licensed under the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Important:** Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for specific language governing permissions and limitations under the License.
|
**Important:** Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Links
|
## Links
|
||||||
|
|
||||||
- [GitHub Repository](https://github.com/pese-git/cherrypick)
|
- [GitHub Repository](https://github.com/pese-git/cherrypick)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class AppModule extends Module {
|
|||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
// Set a global logger for the DI system
|
// Set a global logger for the DI system
|
||||||
CherryPick.setGlobalLogger(PrintLogger());
|
CherryPick.setGlobalObserver(PrintCherryPickObserver());
|
||||||
|
|
||||||
// Open the root scope
|
// Open the root scope
|
||||||
final rootScope = CherryPick.openRootScope();
|
final rootScope = CherryPick.openRootScope();
|
||||||
@@ -32,6 +32,6 @@ void main() {
|
|||||||
subScope.closeSubScope('feature.profile');
|
subScope.closeSubScope('feature.profile');
|
||||||
|
|
||||||
// Demonstrate disabling and re-enabling logging
|
// Demonstrate disabling and re-enabling logging
|
||||||
CherryPick.setGlobalLogger(const SilentLogger());
|
CherryPick.setGlobalObserver(SilentCherryPickObserver());
|
||||||
rootScope.resolve<UserRepository>(); // now without logs
|
rootScope.resolve<UserRepository>(); // now without logs
|
||||||
}
|
}
|
||||||
|
|||||||
40
cherrypick/example/disposable_example.dart
Normal file
40
cherrypick/example/disposable_example.dart
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
|
||||||
|
/// Ваш сервис с освобождением ресурсов
|
||||||
|
class MyService implements Disposable {
|
||||||
|
bool wasDisposed = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
// Например: закрыть соединение, остановить таймер, освободить память
|
||||||
|
wasDisposed = true;
|
||||||
|
print('MyService disposed!');
|
||||||
|
}
|
||||||
|
|
||||||
|
void doSomething() => print('Doing something...');
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
final scope = CherryPick.openRootScope();
|
||||||
|
|
||||||
|
// Регистрируем биндинг (singleton для примера)
|
||||||
|
scope.installModules([
|
||||||
|
ModuleImpl(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Получаем зависимость
|
||||||
|
final service = scope.resolve<MyService>();
|
||||||
|
service.doSomething(); // «Doing something...»
|
||||||
|
|
||||||
|
// Освобождаем все ресурсы
|
||||||
|
scope.dispose();
|
||||||
|
print('Service wasDisposed = ${service.wasDisposed}'); // true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Пример модуля CherryPick
|
||||||
|
class ModuleImpl extends Module {
|
||||||
|
@override
|
||||||
|
void builder(Scope scope) {
|
||||||
|
bind<MyService>().toProvide(() => MyService()).singleton();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ library;
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
@@ -20,4 +20,5 @@ export 'package:cherrypick/src/global_cycle_detector.dart';
|
|||||||
export 'package:cherrypick/src/helper.dart';
|
export 'package:cherrypick/src/helper.dart';
|
||||||
export 'package:cherrypick/src/module.dart';
|
export 'package:cherrypick/src/module.dart';
|
||||||
export 'package:cherrypick/src/scope.dart';
|
export 'package:cherrypick/src/scope.dart';
|
||||||
export 'package:cherrypick/src/logger.dart';
|
export 'package:cherrypick/src/disposable.dart';
|
||||||
|
export 'package:cherrypick/src/observer.dart';
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
@@ -13,11 +13,10 @@
|
|||||||
|
|
||||||
import 'package:cherrypick/src/binding_resolver.dart';
|
import 'package:cherrypick/src/binding_resolver.dart';
|
||||||
|
|
||||||
/// RU: Класс Binding<T> настраивает параметры экземпляра.
|
/// RU: Класс Binding<T> настраивает параметры экземпляра.
|
||||||
/// ENG: The Binding<T> class configures the settings for the instance.
|
/// ENG: The Binding<T> class configures the settings for the instance.
|
||||||
///
|
///
|
||||||
import 'package:cherrypick/src/logger.dart';
|
import 'package:cherrypick/src/observer.dart';
|
||||||
import 'package:cherrypick/src/log_format.dart';
|
|
||||||
|
|
||||||
class Binding<T> {
|
class Binding<T> {
|
||||||
late Type _key;
|
late Type _key;
|
||||||
@@ -25,50 +24,54 @@ class Binding<T> {
|
|||||||
|
|
||||||
BindingResolver<T>? _resolver;
|
BindingResolver<T>? _resolver;
|
||||||
|
|
||||||
CherryPickLogger? logger;
|
CherryPickObserver? observer;
|
||||||
|
|
||||||
// Deferred logging flags
|
// Deferred logging flags
|
||||||
bool _createdLogged = false;
|
bool _createdLogged = false;
|
||||||
bool _namedLogged = false;
|
bool _namedLogged = false;
|
||||||
bool _singletonLogged = false;
|
bool _singletonLogged = false;
|
||||||
|
|
||||||
Binding({this.logger}) {
|
Binding({this.observer}) {
|
||||||
_key = T;
|
_key = T;
|
||||||
// Не логируем здесь! Делаем deferred лог после назначения logger
|
// Deferred уведомения observer, не логировать здесь напрямую
|
||||||
}
|
}
|
||||||
|
|
||||||
void markCreated() {
|
void markCreated() {
|
||||||
if (!_createdLogged) {
|
if (!_createdLogged) {
|
||||||
logger?.info(formatLogMessage(
|
observer?.onBindingRegistered(
|
||||||
type: 'Binding',
|
runtimeType.toString(),
|
||||||
name: T.toString(),
|
T,
|
||||||
params: _name != null ? {'name': _name} : null,
|
);
|
||||||
description: 'created',
|
|
||||||
));
|
|
||||||
_createdLogged = true;
|
_createdLogged = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void markNamed() {
|
void markNamed() {
|
||||||
if (isNamed && !_namedLogged) {
|
if (isNamed && !_namedLogged) {
|
||||||
logger?.info(formatLogMessage(
|
observer?.onDiagnostic(
|
||||||
type: 'Binding',
|
'Binding named: ${T.toString()} name: $_name',
|
||||||
name: T.toString(),
|
details: {
|
||||||
params: {'name': _name},
|
'type': 'Binding',
|
||||||
description: 'named',
|
'name': T.toString(),
|
||||||
));
|
'nameParam': _name,
|
||||||
|
'description': 'named',
|
||||||
|
},
|
||||||
|
);
|
||||||
_namedLogged = true;
|
_namedLogged = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void markSingleton() {
|
void markSingleton() {
|
||||||
if (isSingleton && !_singletonLogged) {
|
if (isSingleton && !_singletonLogged) {
|
||||||
logger?.info(formatLogMessage(
|
observer?.onDiagnostic(
|
||||||
type: 'Binding',
|
'Binding singleton: ${T.toString()}${_name != null ? ' name: $_name' : ''}',
|
||||||
name: T.toString(),
|
details: {
|
||||||
params: _name != null ? {'name': _name} : null,
|
'type': 'Binding',
|
||||||
description: 'singleton mode enabled',
|
'name': T.toString(),
|
||||||
));
|
if (_name != null) 'name': _name,
|
||||||
|
'description': 'singleton mode enabled',
|
||||||
|
},
|
||||||
|
);
|
||||||
_singletonLogged = true;
|
_singletonLogged = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -170,25 +173,23 @@ class Binding<T> {
|
|||||||
T? resolveSync([dynamic params]) {
|
T? resolveSync([dynamic params]) {
|
||||||
final res = resolver?.resolveSync(params);
|
final res = resolver?.resolveSync(params);
|
||||||
if (res != null) {
|
if (res != null) {
|
||||||
logger?.info(formatLogMessage(
|
observer?.onDiagnostic(
|
||||||
type: 'Binding',
|
'Binding resolved instance: ${T.toString()}',
|
||||||
name: T.toString(),
|
details: {
|
||||||
params: {
|
|
||||||
if (_name != null) 'name': _name,
|
if (_name != null) 'name': _name,
|
||||||
'method': 'resolveSync',
|
'method': 'resolveSync',
|
||||||
|
'description': 'object created/resolved',
|
||||||
},
|
},
|
||||||
description: 'object created/resolved',
|
);
|
||||||
));
|
|
||||||
} else {
|
} else {
|
||||||
logger?.warn(formatLogMessage(
|
observer?.onWarning(
|
||||||
type: 'Binding',
|
'resolveSync returned null: ${T.toString()}',
|
||||||
name: T.toString(),
|
details: {
|
||||||
params: {
|
|
||||||
if (_name != null) 'name': _name,
|
if (_name != null) 'name': _name,
|
||||||
'method': 'resolveSync',
|
'method': 'resolveSync',
|
||||||
|
'description': 'resolveSync returned null',
|
||||||
},
|
},
|
||||||
description: 'resolveSync returned null',
|
);
|
||||||
));
|
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@@ -197,38 +198,28 @@ class Binding<T> {
|
|||||||
final future = resolver?.resolveAsync(params);
|
final future = resolver?.resolveAsync(params);
|
||||||
if (future != null) {
|
if (future != null) {
|
||||||
future
|
future
|
||||||
.then((res) => logger?.info(formatLogMessage(
|
.then((res) => observer?.onDiagnostic(
|
||||||
type: 'Binding',
|
'Future resolved for: ${T.toString()}',
|
||||||
name: T.toString(),
|
details: {
|
||||||
params: {
|
|
||||||
if (_name != null) 'name': _name,
|
if (_name != null) 'name': _name,
|
||||||
'method': 'resolveAsync',
|
'method': 'resolveAsync',
|
||||||
|
'description': 'Future resolved',
|
||||||
},
|
},
|
||||||
description: 'Future resolved',
|
))
|
||||||
)))
|
.catchError((e, s) => observer?.onError(
|
||||||
.catchError((e, s) => logger?.error(
|
'resolveAsync error: ${T.toString()}',
|
||||||
formatLogMessage(
|
|
||||||
type: 'Binding',
|
|
||||||
name: T.toString(),
|
|
||||||
params: {
|
|
||||||
if (_name != null) 'name': _name,
|
|
||||||
'method': 'resolveAsync',
|
|
||||||
},
|
|
||||||
description: 'resolveAsync error',
|
|
||||||
),
|
|
||||||
e,
|
e,
|
||||||
s,
|
s,
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
logger?.warn(formatLogMessage(
|
observer?.onWarning(
|
||||||
type: 'Binding',
|
'resolveAsync returned null: ${T.toString()}',
|
||||||
name: T.toString(),
|
details: {
|
||||||
params: {
|
|
||||||
if (_name != null) 'name': _name,
|
if (_name != null) 'name': _name,
|
||||||
'method': 'resolveAsync',
|
'method': 'resolveAsync',
|
||||||
|
'description': 'resolveAsync returned null',
|
||||||
},
|
},
|
||||||
description: 'resolveAsync returned null',
|
);
|
||||||
));
|
|
||||||
}
|
}
|
||||||
return future;
|
return future;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2021 Sergey Penkovsky (sergey.penkovsky@gmail.com)
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
typedef Instance<T> = FutureOr<T>;
|
typedef Instance<T> = FutureOr<T>;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
@@ -12,8 +12,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
import 'package:cherrypick/src/logger.dart';
|
import 'package:cherrypick/src/observer.dart';
|
||||||
import 'package:cherrypick/src/log_format.dart';
|
|
||||||
|
|
||||||
/// RU: Исключение, выбрасываемое при обнаружении циклической зависимости.
|
/// RU: Исключение, выбрасываемое при обнаружении циклической зависимости.
|
||||||
/// ENG: Exception thrown when a circular dependency is detected.
|
/// ENG: Exception thrown when a circular dependency is detected.
|
||||||
@@ -36,11 +35,11 @@ class CircularDependencyException implements Exception {
|
|||||||
/// RU: Детектор циклических зависимостей для CherryPick DI контейнера.
|
/// RU: Детектор циклических зависимостей для CherryPick DI контейнера.
|
||||||
/// ENG: Circular dependency detector for CherryPick DI container.
|
/// ENG: Circular dependency detector for CherryPick DI container.
|
||||||
class CycleDetector {
|
class CycleDetector {
|
||||||
final CherryPickLogger _logger;
|
final CherryPickObserver _observer;
|
||||||
final Set<String> _resolutionStack = HashSet<String>();
|
final Set<String> _resolutionStack = HashSet<String>();
|
||||||
final List<String> _resolutionHistory = [];
|
final List<String> _resolutionHistory = [];
|
||||||
|
|
||||||
CycleDetector({required CherryPickLogger logger}): _logger = logger;
|
CycleDetector({required CherryPickObserver observer}) : _observer = observer;
|
||||||
|
|
||||||
/// RU: Начинает отслеживание разрешения зависимости.
|
/// RU: Начинает отслеживание разрешения зависимости.
|
||||||
/// ENG: Starts tracking dependency resolution.
|
/// ENG: Starts tracking dependency resolution.
|
||||||
@@ -48,25 +47,24 @@ class CycleDetector {
|
|||||||
/// Throws [CircularDependencyException] if circular dependency is detected.
|
/// Throws [CircularDependencyException] if circular dependency is detected.
|
||||||
void startResolving<T>({String? named}) {
|
void startResolving<T>({String? named}) {
|
||||||
final dependencyKey = _createDependencyKey<T>(named);
|
final dependencyKey = _createDependencyKey<T>(named);
|
||||||
print('[DEBUG] CycleDetector logger type=${_logger.runtimeType} hash=${_logger.hashCode}');
|
_observer.onDiagnostic(
|
||||||
_logger.info(formatLogMessage(
|
'CycleDetector startResolving: $dependencyKey',
|
||||||
type: 'CycleDetector',
|
details: {
|
||||||
name: dependencyKey.toString(),
|
'event': 'startResolving',
|
||||||
params: {'event': 'startResolving', 'stackSize': _resolutionStack.length},
|
'stackSize': _resolutionStack.length,
|
||||||
description: 'start resolving',
|
},
|
||||||
));
|
);
|
||||||
if (_resolutionStack.contains(dependencyKey)) {
|
if (_resolutionStack.contains(dependencyKey)) {
|
||||||
// Найдена циклическая зависимость
|
|
||||||
final cycleStartIndex = _resolutionHistory.indexOf(dependencyKey);
|
final cycleStartIndex = _resolutionHistory.indexOf(dependencyKey);
|
||||||
final cycle = _resolutionHistory.sublist(cycleStartIndex)..add(dependencyKey);
|
final cycle = _resolutionHistory.sublist(cycleStartIndex)..add(dependencyKey);
|
||||||
// print removed (trace)
|
_observer.onCycleDetected(
|
||||||
final msg = formatLogMessage(
|
cycle,
|
||||||
type: 'CycleDetector',
|
);
|
||||||
name: dependencyKey.toString(),
|
_observer.onError(
|
||||||
params: {'chain': cycle.join('->')},
|
'Cycle detected for $dependencyKey',
|
||||||
description: 'cycle detected',
|
null,
|
||||||
|
null,
|
||||||
);
|
);
|
||||||
_logger.error(msg);
|
|
||||||
throw CircularDependencyException(
|
throw CircularDependencyException(
|
||||||
'Circular dependency detected for $dependencyKey',
|
'Circular dependency detected for $dependencyKey',
|
||||||
cycle,
|
cycle,
|
||||||
@@ -81,12 +79,10 @@ class CycleDetector {
|
|||||||
/// ENG: Finishes tracking dependency resolution.
|
/// ENG: Finishes tracking dependency resolution.
|
||||||
void finishResolving<T>({String? named}) {
|
void finishResolving<T>({String? named}) {
|
||||||
final dependencyKey = _createDependencyKey<T>(named);
|
final dependencyKey = _createDependencyKey<T>(named);
|
||||||
_logger.info(formatLogMessage(
|
_observer.onDiagnostic(
|
||||||
type: 'CycleDetector',
|
'CycleDetector finishResolving: $dependencyKey',
|
||||||
name: dependencyKey.toString(),
|
details: {'event': 'finishResolving'},
|
||||||
params: {'event': 'finishResolving'},
|
);
|
||||||
description: 'finish resolving',
|
|
||||||
));
|
|
||||||
_resolutionStack.remove(dependencyKey);
|
_resolutionStack.remove(dependencyKey);
|
||||||
// Удаляем из истории только если это последний элемент
|
// Удаляем из истории только если это последний элемент
|
||||||
if (_resolutionHistory.isNotEmpty &&
|
if (_resolutionHistory.isNotEmpty &&
|
||||||
@@ -98,11 +94,13 @@ class CycleDetector {
|
|||||||
/// RU: Очищает все состояние детектора.
|
/// RU: Очищает все состояние детектора.
|
||||||
/// ENG: Clears all detector state.
|
/// ENG: Clears all detector state.
|
||||||
void clear() {
|
void clear() {
|
||||||
_logger.info(formatLogMessage(
|
_observer.onDiagnostic(
|
||||||
type: 'CycleDetector',
|
'CycleDetector clear',
|
||||||
params: {'event': 'clear'},
|
details: {
|
||||||
description: 'resolution stack cleared',
|
'event': 'clear',
|
||||||
));
|
'description': 'resolution stack cleared',
|
||||||
|
},
|
||||||
|
);
|
||||||
_resolutionStack.clear();
|
_resolutionStack.clear();
|
||||||
_resolutionHistory.clear();
|
_resolutionHistory.clear();
|
||||||
}
|
}
|
||||||
@@ -130,28 +128,32 @@ class CycleDetector {
|
|||||||
/// ENG: Mixin for adding circular dependency detection support.
|
/// ENG: Mixin for adding circular dependency detection support.
|
||||||
mixin CycleDetectionMixin {
|
mixin CycleDetectionMixin {
|
||||||
CycleDetector? _cycleDetector;
|
CycleDetector? _cycleDetector;
|
||||||
CherryPickLogger get logger;
|
CherryPickObserver get observer;
|
||||||
|
|
||||||
/// RU: Включает обнаружение циклических зависимостей.
|
/// RU: Включает обнаружение циклических зависимостей.
|
||||||
/// ENG: Enables circular dependency detection.
|
/// ENG: Enables circular dependency detection.
|
||||||
void enableCycleDetection() {
|
void enableCycleDetection() {
|
||||||
_cycleDetector = CycleDetector(logger: logger);
|
_cycleDetector = CycleDetector(observer: observer);
|
||||||
logger.info(formatLogMessage(
|
observer.onDiagnostic(
|
||||||
type: 'CycleDetection',
|
'CycleDetection enabled',
|
||||||
params: {'event': 'enable'},
|
details: {
|
||||||
description: 'cycle detection enabled',
|
'event': 'enable',
|
||||||
));
|
'description': 'cycle detection enabled',
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Отключает обнаружение циклических зависимостей.
|
/// RU: Отключает обнаружение циклических зависимостей.
|
||||||
/// ENG: Disables circular dependency detection.
|
/// ENG: Disables circular dependency detection.
|
||||||
void disableCycleDetection() {
|
void disableCycleDetection() {
|
||||||
_cycleDetector?.clear();
|
_cycleDetector?.clear();
|
||||||
logger.info(formatLogMessage(
|
observer.onDiagnostic(
|
||||||
type: 'CycleDetection',
|
'CycleDetection disabled',
|
||||||
params: {'event': 'disable'},
|
details: {
|
||||||
description: 'cycle detection disabled',
|
'event': 'disable',
|
||||||
));
|
'description': 'cycle detection disabled',
|
||||||
|
},
|
||||||
|
);
|
||||||
_cycleDetector = null;
|
_cycleDetector = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,12 +180,14 @@ mixin CycleDetectionMixin {
|
|||||||
final cycleStartIndex = _cycleDetector!._resolutionHistory.indexOf(dependencyKey);
|
final cycleStartIndex = _cycleDetector!._resolutionHistory.indexOf(dependencyKey);
|
||||||
final cycle = _cycleDetector!._resolutionHistory.sublist(cycleStartIndex)
|
final cycle = _cycleDetector!._resolutionHistory.sublist(cycleStartIndex)
|
||||||
..add(dependencyKey);
|
..add(dependencyKey);
|
||||||
logger.error(formatLogMessage(
|
observer.onCycleDetected(
|
||||||
type: 'CycleDetector',
|
cycle,
|
||||||
name: dependencyKey.toString(),
|
);
|
||||||
params: {'chain': cycle.join('->')},
|
observer.onError(
|
||||||
description: 'cycle detected',
|
'Cycle detected for $dependencyKey',
|
||||||
));
|
null,
|
||||||
|
null,
|
||||||
|
);
|
||||||
throw CircularDependencyException(
|
throw CircularDependencyException(
|
||||||
'Circular dependency detected for $dependencyKey',
|
'Circular dependency detected for $dependencyKey',
|
||||||
cycle,
|
cycle,
|
||||||
|
|||||||
63
cherrypick/lib/src/disposable.dart
Normal file
63
cherrypick/lib/src/disposable.dart
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2021 Sergey Penkovsky (sergey.penkovsky@gmail.com)
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
/// An interface for resources that require explicit cleanup, used by the CherryPick dependency injection container.
|
||||||
|
///
|
||||||
|
/// If an object implements [Disposable], the CherryPick DI container will automatically call [dispose]
|
||||||
|
/// when the corresponding scope is cleaned up. Use [Disposable] for closing streams, files, controllers, services, etc.
|
||||||
|
/// Both synchronous and asynchronous cleanup is supported:
|
||||||
|
/// - For sync disposables, implement [dispose] as a `void` or simply return nothing.
|
||||||
|
/// - For async disposables, implement [dispose] returning a [Future].
|
||||||
|
///
|
||||||
|
/// Example: Synchronous Disposable
|
||||||
|
/// ```dart
|
||||||
|
/// class MyLogger implements Disposable {
|
||||||
|
/// void dispose() {
|
||||||
|
/// print('Logger closed!');
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Example: Asynchronous Disposable
|
||||||
|
/// ```dart
|
||||||
|
/// class MyConnection implements Disposable {
|
||||||
|
/// @override
|
||||||
|
/// Future<void> dispose() async {
|
||||||
|
/// await connection.close();
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Usage with CherryPick DI Module
|
||||||
|
/// ```dart
|
||||||
|
/// final scope = openRootScope();
|
||||||
|
/// scope.installModules([
|
||||||
|
/// Module((b) {
|
||||||
|
/// b.bind<MyLogger>((_) => MyLogger());
|
||||||
|
/// b.bindAsync<MyConnection>((_) async => MyConnection());
|
||||||
|
/// }),
|
||||||
|
/// ]);
|
||||||
|
/// // ...
|
||||||
|
/// await scope.close(); // will automatically call dispose on all Disposables
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// This pattern ensures that all resources are released safely and automatically when the scope is destroyed.
|
||||||
|
abstract class Disposable {
|
||||||
|
/// Releases all resources held by this object.
|
||||||
|
///
|
||||||
|
/// Implement cleanup logic (closing streams, sockets, files, etc.) within this method.
|
||||||
|
/// Return a [Future] for async cleanup, or nothing (`void`) for synchronous cleanup.
|
||||||
|
FutureOr<void> dispose();
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
@@ -13,7 +13,6 @@
|
|||||||
|
|
||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
import 'package:cherrypick/cherrypick.dart';
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
import 'package:cherrypick/src/log_format.dart';
|
|
||||||
|
|
||||||
|
|
||||||
/// RU: Глобальный детектор циклических зависимостей для всей иерархии скоупов.
|
/// RU: Глобальный детектор циклических зависимостей для всей иерархии скоупов.
|
||||||
@@ -21,7 +20,7 @@ import 'package:cherrypick/src/log_format.dart';
|
|||||||
class GlobalCycleDetector {
|
class GlobalCycleDetector {
|
||||||
static GlobalCycleDetector? _instance;
|
static GlobalCycleDetector? _instance;
|
||||||
|
|
||||||
final CherryPickLogger _logger;
|
final CherryPickObserver _observer;
|
||||||
|
|
||||||
// Глобальный стек разрешения зависимостей
|
// Глобальный стек разрешения зависимостей
|
||||||
final Set<String> _globalResolutionStack = HashSet<String>();
|
final Set<String> _globalResolutionStack = HashSet<String>();
|
||||||
@@ -32,12 +31,12 @@ class GlobalCycleDetector {
|
|||||||
// Карта активных детекторов по скоупам
|
// Карта активных детекторов по скоупам
|
||||||
final Map<String, CycleDetector> _scopeDetectors = HashMap<String, CycleDetector>();
|
final Map<String, CycleDetector> _scopeDetectors = HashMap<String, CycleDetector>();
|
||||||
|
|
||||||
GlobalCycleDetector._internal({required CherryPickLogger logger}): _logger = logger;
|
GlobalCycleDetector._internal({required CherryPickObserver observer}): _observer = observer;
|
||||||
|
|
||||||
/// RU: Получить единственный экземпляр глобального детектора.
|
/// RU: Получить единственный экземпляр глобального детектора.
|
||||||
/// ENG: Get singleton instance of global detector.
|
/// ENG: Get singleton instance of global detector.
|
||||||
static GlobalCycleDetector get instance {
|
static GlobalCycleDetector get instance {
|
||||||
_instance ??= GlobalCycleDetector._internal(logger: CherryPick.globalLogger);
|
_instance ??= GlobalCycleDetector._internal(observer: CherryPick.globalObserver);
|
||||||
return _instance!;
|
return _instance!;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,12 +58,15 @@ class GlobalCycleDetector {
|
|||||||
// Найдена глобальная циклическая зависимость
|
// Найдена глобальная циклическая зависимость
|
||||||
final cycleStartIndex = _globalResolutionHistory.indexOf(dependencyKey);
|
final cycleStartIndex = _globalResolutionHistory.indexOf(dependencyKey);
|
||||||
final cycle = _globalResolutionHistory.sublist(cycleStartIndex)..add(dependencyKey);
|
final cycle = _globalResolutionHistory.sublist(cycleStartIndex)..add(dependencyKey);
|
||||||
_logger.error(formatLogMessage(
|
_observer.onCycleDetected(
|
||||||
type: 'CycleDetector',
|
cycle,
|
||||||
name: dependencyKey.toString(),
|
scopeName: scopeId,
|
||||||
params: {'chain': cycle.join('->')},
|
);
|
||||||
description: 'cycle detected',
|
_observer.onError(
|
||||||
));
|
'Global circular dependency detected for $dependencyKey',
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
);
|
||||||
throw CircularDependencyException(
|
throw CircularDependencyException(
|
||||||
'Global circular dependency detected for $dependencyKey',
|
'Global circular dependency detected for $dependencyKey',
|
||||||
cycle,
|
cycle,
|
||||||
@@ -102,12 +104,15 @@ class GlobalCycleDetector {
|
|||||||
final cycleStartIndex = _globalResolutionHistory.indexOf(dependencyKey);
|
final cycleStartIndex = _globalResolutionHistory.indexOf(dependencyKey);
|
||||||
final cycle = _globalResolutionHistory.sublist(cycleStartIndex)
|
final cycle = _globalResolutionHistory.sublist(cycleStartIndex)
|
||||||
..add(dependencyKey);
|
..add(dependencyKey);
|
||||||
_logger.error(formatLogMessage(
|
_observer.onCycleDetected(
|
||||||
type: 'CycleDetector',
|
cycle,
|
||||||
name: dependencyKey.toString(),
|
scopeName: scopeId,
|
||||||
params: {'chain': cycle.join('->')},
|
);
|
||||||
description: 'cycle detected',
|
_observer.onError(
|
||||||
));
|
'Global circular dependency detected for $dependencyKey',
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
);
|
||||||
throw CircularDependencyException(
|
throw CircularDependencyException(
|
||||||
'Global circular dependency detected for $dependencyKey',
|
'Global circular dependency detected for $dependencyKey',
|
||||||
cycle,
|
cycle,
|
||||||
@@ -131,7 +136,7 @@ class GlobalCycleDetector {
|
|||||||
/// RU: Получить детектор для конкретного скоупа.
|
/// RU: Получить детектор для конкретного скоупа.
|
||||||
/// ENG: Get detector for specific scope.
|
/// ENG: Get detector for specific scope.
|
||||||
CycleDetector getScopeDetector(String scopeId) {
|
CycleDetector getScopeDetector(String scopeId) {
|
||||||
return _scopeDetectors.putIfAbsent(scopeId, () => CycleDetector(logger: CherryPick.globalLogger));
|
return _scopeDetectors.putIfAbsent(scopeId, () => CycleDetector(observer: CherryPick.globalObserver));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Удалить детектор для скоупа.
|
/// RU: Удалить детектор для скоупа.
|
||||||
|
|||||||
@@ -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,7 +13,7 @@
|
|||||||
|
|
||||||
import 'package:cherrypick/src/scope.dart';
|
import 'package:cherrypick/src/scope.dart';
|
||||||
import 'package:cherrypick/src/global_cycle_detector.dart';
|
import 'package:cherrypick/src/global_cycle_detector.dart';
|
||||||
import 'package:cherrypick/src/logger.dart';
|
import 'package:cherrypick/src/observer.dart';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ Scope? _rootScope;
|
|||||||
/// Global logger for all [Scope]s managed by [CherryPick].
|
/// Global logger for all [Scope]s managed by [CherryPick].
|
||||||
///
|
///
|
||||||
/// Defaults to [SilentLogger] unless set via [setGlobalLogger].
|
/// Defaults to [SilentLogger] unless set via [setGlobalLogger].
|
||||||
CherryPickLogger _globalLogger = const SilentLogger();
|
CherryPickObserver _globalObserver = SilentCherryPickObserver();
|
||||||
|
|
||||||
/// Whether global local-cycle detection is enabled for all Scopes ([Scope.enableCycleDetection]).
|
/// Whether global local-cycle detection is enabled for all Scopes ([Scope.enableCycleDetection]).
|
||||||
bool _globalCycleDetectionEnabled = false;
|
bool _globalCycleDetectionEnabled = false;
|
||||||
@@ -59,12 +59,12 @@ class CherryPick {
|
|||||||
/// ```dart
|
/// ```dart
|
||||||
/// CherryPick.setGlobalLogger(DefaultLogger());
|
/// CherryPick.setGlobalLogger(DefaultLogger());
|
||||||
/// ```
|
/// ```
|
||||||
static void setGlobalLogger(CherryPickLogger logger) {
|
static void setGlobalObserver(CherryPickObserver observer) {
|
||||||
_globalLogger = logger;
|
_globalObserver = observer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the current global logger used by CherryPick.
|
/// Returns the current global logger used by CherryPick.
|
||||||
static CherryPickLogger get globalLogger => _globalLogger;
|
static CherryPickObserver get globalObserver => _globalObserver;
|
||||||
|
|
||||||
/// Returns the singleton root [Scope], creating it if needed.
|
/// Returns the singleton root [Scope], creating it if needed.
|
||||||
///
|
///
|
||||||
@@ -75,7 +75,7 @@ class CherryPick {
|
|||||||
/// final root = CherryPick.openRootScope();
|
/// final root = CherryPick.openRootScope();
|
||||||
/// ```
|
/// ```
|
||||||
static Scope openRootScope() {
|
static Scope openRootScope() {
|
||||||
_rootScope ??= Scope(null, logger: _globalLogger);
|
_rootScope ??= Scope(null, observer: _globalObserver);
|
||||||
// Apply cycle detection settings
|
// Apply cycle detection settings
|
||||||
if (_globalCycleDetectionEnabled && !_rootScope!.isCycleDetectionEnabled) {
|
if (_globalCycleDetectionEnabled && !_rootScope!.isCycleDetectionEnabled) {
|
||||||
_rootScope!.enableCycleDetection();
|
_rootScope!.enableCycleDetection();
|
||||||
@@ -94,8 +94,11 @@ class CherryPick {
|
|||||||
/// ```dart
|
/// ```dart
|
||||||
/// CherryPick.closeRootScope();
|
/// CherryPick.closeRootScope();
|
||||||
/// ```
|
/// ```
|
||||||
static void closeRootScope() {
|
static Future<void> closeRootScope() async {
|
||||||
_rootScope = null;
|
if (_rootScope != null) {
|
||||||
|
await _rootScope!.dispose(); // Автоматический вызов dispose для rootScope!
|
||||||
|
_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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright 2021 Sergey Penkovsky (sergey.penkovsky@gmail.com)
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
//
|
|
||||||
|
|
||||||
|
|
||||||
/// Formats a log message string for CherryPick's logging system.
|
|
||||||
///
|
|
||||||
/// This function provides a unified structure for framework logs (info, warn, error, debug, etc.),
|
|
||||||
/// making it easier to parse and analyze events related to DI operations such as resolving bindings,
|
|
||||||
/// scope creation, module installation, etc.
|
|
||||||
///
|
|
||||||
/// All parameters except [name] and [params] are required.
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
/// ```dart
|
|
||||||
/// final msg = formatLogMessage(
|
|
||||||
/// type: 'Binding',
|
|
||||||
/// name: 'MyService',
|
|
||||||
/// params: {'parent': 'AppModule', 'lifecycle': 'singleton'},
|
|
||||||
/// description: 'created',
|
|
||||||
/// );
|
|
||||||
/// // Result: [Binding:MyService] parent=AppModule lifecycle=singleton created
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// Parameters:
|
|
||||||
/// - [type]: The type of the log event subject (e.g., 'Binding', 'Scope', 'Module'). Required.
|
|
||||||
/// - [name]: Optional name of the subject (binding/scope/module) to disambiguate multiple instances/objects.
|
|
||||||
/// - [params]: Optional map for additional context (e.g., id, parent, lifecycle, named, etc.).
|
|
||||||
/// - [description]: Concise description of the event. Required.
|
|
||||||
///
|
|
||||||
/// Returns a structured string:
|
|
||||||
/// [type(:name)] param1=val1 param2=val2 ... description
|
|
||||||
String formatLogMessage({
|
|
||||||
required String type, // Binding, Scope, Module, ...
|
|
||||||
String? name, // Имя binding/scope/module
|
|
||||||
Map<String, Object?>? params, // Дополнительные параметры (id, parent, named и др.)
|
|
||||||
required String description, // Краткое описание события
|
|
||||||
}) {
|
|
||||||
final label = name != null ? '$type:$name' : type;
|
|
||||||
final paramsStr = (params != null && params.isNotEmpty)
|
|
||||||
? params.entries.map((e) => '${e.key}=${e.value}').join(' ')
|
|
||||||
: '';
|
|
||||||
return '[$label]'
|
|
||||||
'${paramsStr.isNotEmpty ? ' $paramsStr' : ''}'
|
|
||||||
' $description';
|
|
||||||
}
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright 2021 Sergey Penkovsky (sergey.penkovsky@gmail.com)
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
//
|
|
||||||
|
|
||||||
/// ----------------------------------------------------------------------------
|
|
||||||
/// CherryPickLogger — интерфейс для логирования событий DI в CherryPick.
|
|
||||||
///
|
|
||||||
/// ENGLISH:
|
|
||||||
/// Interface for dependency injection (DI) logger in CherryPick. Allows you to
|
|
||||||
/// receive information about the internal events and errors in the DI system.
|
|
||||||
/// Your implementation can use any logging framework or UI.
|
|
||||||
///
|
|
||||||
/// RUSSIAN:
|
|
||||||
/// Интерфейс логгера для DI-контейнера CherryPick. Позволяет получать
|
|
||||||
/// сообщения о работе DI-контейнера, его ошибках и событиях, и
|
|
||||||
/// интегрировать любые готовые решения для логирования/сбора ошибок.
|
|
||||||
/// ----------------------------------------------------------------------------
|
|
||||||
abstract class CherryPickLogger {
|
|
||||||
/// ----------------------------------------------------------------------------
|
|
||||||
/// info — Информационное сообщение.
|
|
||||||
///
|
|
||||||
/// ENGLISH:
|
|
||||||
/// Logs an informational message about DI operation or state.
|
|
||||||
///
|
|
||||||
/// RUSSIAN:
|
|
||||||
/// Логирование информационного сообщения о событиях DI.
|
|
||||||
/// ----------------------------------------------------------------------------
|
|
||||||
void info(String message);
|
|
||||||
|
|
||||||
/// ----------------------------------------------------------------------------
|
|
||||||
/// warn — Предупреждение.
|
|
||||||
///
|
|
||||||
/// ENGLISH:
|
|
||||||
/// Logs a warning related to DI events (for example, possible misconfiguration).
|
|
||||||
///
|
|
||||||
/// RUSSIAN:
|
|
||||||
/// Логирование предупреждения, связанного с DI (например, возможная ошибка
|
|
||||||
/// конфигурации).
|
|
||||||
/// ----------------------------------------------------------------------------
|
|
||||||
void warn(String message);
|
|
||||||
|
|
||||||
/// ----------------------------------------------------------------------------
|
|
||||||
/// error — Ошибка.
|
|
||||||
///
|
|
||||||
/// ENGLISH:
|
|
||||||
/// Logs an error message, may include error object and stack trace.
|
|
||||||
///
|
|
||||||
/// RUSSIAN:
|
|
||||||
/// Логирование ошибки, дополнительно может содержать объект ошибки
|
|
||||||
/// и StackTrace.
|
|
||||||
/// ----------------------------------------------------------------------------
|
|
||||||
void error(String message, [Object? error, StackTrace? stackTrace]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ----------------------------------------------------------------------------
|
|
||||||
/// SilentLogger — «тихий» логгер CherryPick. Сообщения игнорируются.
|
|
||||||
///
|
|
||||||
/// ENGLISH:
|
|
||||||
/// SilentLogger ignores all log messages. Used by default in production to
|
|
||||||
/// avoid polluting logs with DI events.
|
|
||||||
///
|
|
||||||
/// RUSSIAN:
|
|
||||||
/// SilentLogger игнорирует все события логгирования. Используется по умолчанию
|
|
||||||
/// на production, чтобы не засорять логи техническими сообщениями DI.
|
|
||||||
/// ----------------------------------------------------------------------------
|
|
||||||
class SilentLogger implements CherryPickLogger {
|
|
||||||
const SilentLogger();
|
|
||||||
@override
|
|
||||||
void info(String message) {}
|
|
||||||
@override
|
|
||||||
void warn(String message) {}
|
|
||||||
@override
|
|
||||||
void error(String message, [Object? error, StackTrace? stackTrace]) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ----------------------------------------------------------------------------
|
|
||||||
/// PrintLogger — логгер CherryPick, выводящий все сообщения через print.
|
|
||||||
///
|
|
||||||
/// ENGLISH:
|
|
||||||
/// PrintLogger outputs all log messages to the console using `print()`.
|
|
||||||
/// Suitable for debugging, prototyping, or simple console applications.
|
|
||||||
///
|
|
||||||
/// RUSSIAN:
|
|
||||||
/// PrintLogger выводит все сообщения (info, warn, error) в консоль через print.
|
|
||||||
/// Удобен для отладки или консольных приложений.
|
|
||||||
/// ----------------------------------------------------------------------------
|
|
||||||
class PrintLogger implements CherryPickLogger {
|
|
||||||
const PrintLogger();
|
|
||||||
@override
|
|
||||||
void info(String message) => print('[info][CherryPick] $message');
|
|
||||||
@override
|
|
||||||
void warn(String message) => print('[warn][CherryPick] $message');
|
|
||||||
@override
|
|
||||||
void error(String message, [Object? error, StackTrace? stackTrace]) {
|
|
||||||
print('[error][CherryPick] $message');
|
|
||||||
if (error != null) print(' error: $error');
|
|
||||||
if (stackTrace != null) print(' stack: $stackTrace');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
236
cherrypick/lib/src/observer.dart
Normal file
236
cherrypick/lib/src/observer.dart
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2021 Sergey Penkovsky (sergey.penkovsky@gmail.com)
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
/// An abstract Observer for CherryPick DI container events.
|
||||||
|
///
|
||||||
|
/// Extend this class to react to and log various events inside the CherryPick Dependency Injection container.
|
||||||
|
/// Allows monitoring of registration, creation, disposal, module changes, cache hits/misses, cycles, and
|
||||||
|
/// errors/warnings for improved diagnostics and debugging.
|
||||||
|
///
|
||||||
|
/// All methods have detailed event information, including name, type, scope, and other arguments.
|
||||||
|
///
|
||||||
|
/// Example: Logging and debugging container events
|
||||||
|
/// ```dart
|
||||||
|
/// final CherryPickObserver observer = PrintCherryPickObserver();
|
||||||
|
/// // Pass observer to CherryPick during setup
|
||||||
|
/// CherryPick.openRootScope(observer: observer);
|
||||||
|
/// ```
|
||||||
|
abstract class CherryPickObserver {
|
||||||
|
// === Registration and instance lifecycle ===
|
||||||
|
/// Called when a binding is registered within the container (new dependency mapping).
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// observer.onBindingRegistered('MyService', MyService, scopeName: 'root');
|
||||||
|
/// ```
|
||||||
|
void onBindingRegistered(String name, Type type, {String? scopeName});
|
||||||
|
|
||||||
|
/// Called when an instance is requested (before it is created or retrieved from cache).
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// observer.onInstanceRequested('MyService', MyService, scopeName: 'root');
|
||||||
|
/// ```
|
||||||
|
void onInstanceRequested(String name, Type type, {String? scopeName});
|
||||||
|
|
||||||
|
/// Called when a new instance is successfully created.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// observer.onInstanceCreated('MyService', MyService, instance, scopeName: 'root');
|
||||||
|
/// ```
|
||||||
|
void onInstanceCreated(String name, Type type, Object instance, {String? scopeName});
|
||||||
|
|
||||||
|
/// Called when an instance is disposed (removed from cache and/or finalized).
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// observer.onInstanceDisposed('MyService', MyService, instance, scopeName: 'root');
|
||||||
|
/// ```
|
||||||
|
void onInstanceDisposed(String name, Type type, Object instance, {String? scopeName});
|
||||||
|
|
||||||
|
// === Module events ===
|
||||||
|
/// Called when modules are installed into the container.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// observer.onModulesInstalled(['NetworkModule', 'RepositoryModule'], scopeName: 'root');
|
||||||
|
/// ```
|
||||||
|
void onModulesInstalled(List<String> moduleNames, {String? scopeName});
|
||||||
|
|
||||||
|
/// Called when modules are removed from the container.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// observer.onModulesRemoved(['RepositoryModule'], scopeName: 'root');
|
||||||
|
/// ```
|
||||||
|
void onModulesRemoved(List<String> moduleNames, {String? scopeName});
|
||||||
|
|
||||||
|
// === Scope lifecycle ===
|
||||||
|
/// Called when a new DI scope is opened (for example, starting a new feature or screen).
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// observer.onScopeOpened('user-session');
|
||||||
|
/// ```
|
||||||
|
void onScopeOpened(String name);
|
||||||
|
|
||||||
|
/// Called when an existing DI scope is closed.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// observer.onScopeClosed('user-session');
|
||||||
|
/// ```
|
||||||
|
void onScopeClosed(String name);
|
||||||
|
|
||||||
|
// === Cycle detection ===
|
||||||
|
/// Called if a dependency cycle is detected during resolution.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// observer.onCycleDetected(['A', 'B', 'C', 'A'], scopeName: 'root');
|
||||||
|
/// ```
|
||||||
|
void onCycleDetected(List<String> chain, {String? scopeName});
|
||||||
|
|
||||||
|
// === Cache events ===
|
||||||
|
/// Called when an instance is found in the cache.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// observer.onCacheHit('MyService', MyService, scopeName: 'root');
|
||||||
|
/// ```
|
||||||
|
void onCacheHit(String name, Type type, {String? scopeName});
|
||||||
|
|
||||||
|
/// Called when an instance is not found in the cache and should be created.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// observer.onCacheMiss('MyService', MyService, scopeName: 'root');
|
||||||
|
/// ```
|
||||||
|
void onCacheMiss(String name, Type type, {String? scopeName});
|
||||||
|
|
||||||
|
// === Diagnostic ===
|
||||||
|
/// Used for custom diagnostic and debug messages.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// observer.onDiagnostic('Cache cleared', details: detailsObj);
|
||||||
|
/// ```
|
||||||
|
void onDiagnostic(String message, {Object? details});
|
||||||
|
|
||||||
|
// === Warnings & errors ===
|
||||||
|
/// Called on non-fatal, recoverable DI container warnings.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// observer.onWarning('Binding override', details: {...});
|
||||||
|
/// ```
|
||||||
|
void onWarning(String message, {Object? details});
|
||||||
|
|
||||||
|
/// Called on error (typically exceptions thrown during resolution, instantiation, or disposal).
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// observer.onError('Failed to resolve dependency', errorObj, stackTraceObj);
|
||||||
|
/// ```
|
||||||
|
void onError(String message, Object? error, StackTrace? stackTrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Diagnostic/Debug observer that prints all events
|
||||||
|
class PrintCherryPickObserver implements CherryPickObserver {
|
||||||
|
@override
|
||||||
|
void onBindingRegistered(String name, Type type, {String? scopeName}) =>
|
||||||
|
print('[binding][CherryPick] $name — $type (scope: $scopeName)');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInstanceRequested(String name, Type type, {String? scopeName}) =>
|
||||||
|
print('[request][CherryPick] $name — $type (scope: $scopeName)');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInstanceCreated(String name, Type type, Object instance, {String? scopeName}) =>
|
||||||
|
print('[create][CherryPick] $name — $type => $instance (scope: $scopeName)');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInstanceDisposed(String name, Type type, Object instance, {String? scopeName}) =>
|
||||||
|
print('[dispose][CherryPick] $name — $type => $instance (scope: $scopeName)');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onModulesInstalled(List<String> modules, {String? scopeName}) =>
|
||||||
|
print('[modules installed][CherryPick] ${modules.join(', ')} (scope: $scopeName)');
|
||||||
|
@override
|
||||||
|
void onModulesRemoved(List<String> modules, {String? scopeName}) =>
|
||||||
|
print('[modules removed][CherryPick] ${modules.join(', ')} (scope: $scopeName)');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onScopeOpened(String name) => print('[scope opened][CherryPick] $name');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onScopeClosed(String name) => print('[scope closed][CherryPick] $name');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onCycleDetected(List<String> chain, {String? scopeName}) =>
|
||||||
|
print('[cycle][CherryPick] Detected: ${chain.join(' -> ')}${scopeName != null ? ' (scope: $scopeName)' : ''}');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onCacheHit(String name, Type type, {String? scopeName}) =>
|
||||||
|
print('[cache hit][CherryPick] $name — $type (scope: $scopeName)');
|
||||||
|
@override
|
||||||
|
void onCacheMiss(String name, Type type, {String? scopeName}) =>
|
||||||
|
print('[cache miss][CherryPick] $name — $type (scope: $scopeName)');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onDiagnostic(String message, {Object? details}) =>
|
||||||
|
print('[diagnostic][CherryPick] $message ${details ?? ''}');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onWarning(String message, {Object? details}) =>
|
||||||
|
print('[warn][CherryPick] $message ${details ?? ''}');
|
||||||
|
@override
|
||||||
|
void onError(String message, Object? error, StackTrace? stackTrace) {
|
||||||
|
print('[error][CherryPick] $message');
|
||||||
|
if (error != null) print(' error: $error');
|
||||||
|
if (stackTrace != null) print(' stack: $stackTrace');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Silent observer: ignores all events
|
||||||
|
class SilentCherryPickObserver implements CherryPickObserver {
|
||||||
|
@override
|
||||||
|
void onBindingRegistered(String name, Type type, {String? scopeName}) {}
|
||||||
|
@override
|
||||||
|
void onInstanceRequested(String name, Type type, {String? scopeName}) {}
|
||||||
|
@override
|
||||||
|
void onInstanceCreated(String name, Type type, Object instance, {String? scopeName}) {}
|
||||||
|
@override
|
||||||
|
void onInstanceDisposed(String name, Type type, Object instance, {String? scopeName}) {}
|
||||||
|
@override
|
||||||
|
void onModulesInstalled(List<String> modules, {String? scopeName}) {}
|
||||||
|
@override
|
||||||
|
void onModulesRemoved(List<String> modules, {String? scopeName}) {}
|
||||||
|
@override
|
||||||
|
void onScopeOpened(String name) {}
|
||||||
|
@override
|
||||||
|
void onScopeClosed(String name) {}
|
||||||
|
@override
|
||||||
|
void onCycleDetected(List<String> chain, {String? scopeName}) {}
|
||||||
|
@override
|
||||||
|
void onCacheHit(String name, Type type, {String? scopeName}) {}
|
||||||
|
@override
|
||||||
|
void onCacheMiss(String name, Type type, {String? scopeName}) {}
|
||||||
|
@override
|
||||||
|
void onDiagnostic(String message, {Object? details}) {}
|
||||||
|
@override
|
||||||
|
void onWarning(String message, {Object? details}) {}
|
||||||
|
@override
|
||||||
|
void onError(String message, Object? error, StackTrace? stackTrace) {}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
@@ -14,19 +14,23 @@ import 'dart:collection';
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:cherrypick/src/cycle_detector.dart';
|
import 'package:cherrypick/src/cycle_detector.dart';
|
||||||
|
import 'package:cherrypick/src/disposable.dart';
|
||||||
import 'package:cherrypick/src/global_cycle_detector.dart';
|
import 'package:cherrypick/src/global_cycle_detector.dart';
|
||||||
import 'package:cherrypick/src/binding_resolver.dart';
|
import 'package:cherrypick/src/binding_resolver.dart';
|
||||||
import 'package:cherrypick/src/module.dart';
|
import 'package:cherrypick/src/module.dart';
|
||||||
import 'package:cherrypick/src/logger.dart';
|
import 'package:cherrypick/src/observer.dart';
|
||||||
import 'package:cherrypick/src/log_format.dart';
|
// import 'package:cherrypick/src/log_format.dart';
|
||||||
|
|
||||||
class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
||||||
final Scope? _parentScope;
|
final Scope? _parentScope;
|
||||||
|
|
||||||
late final CherryPickLogger _logger;
|
late final CherryPickObserver _observer;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
CherryPickLogger get logger => _logger;
|
CherryPickObserver get observer => _observer;
|
||||||
|
|
||||||
|
/// COLLECTS all resolved instances that implement [Disposable].
|
||||||
|
final Set<Disposable> _disposables = HashSet();
|
||||||
|
|
||||||
/// RU: Метод возвращает родительский [Scope].
|
/// RU: Метод возвращает родительский [Scope].
|
||||||
///
|
///
|
||||||
@@ -37,16 +41,18 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
|
|
||||||
final Map<String, Scope> _scopeMap = HashMap();
|
final Map<String, Scope> _scopeMap = HashMap();
|
||||||
|
|
||||||
Scope(this._parentScope, {required CherryPickLogger logger}) : _logger = logger {
|
Scope(this._parentScope, {required CherryPickObserver observer}) : _observer = observer {
|
||||||
setScopeId(_generateScopeId());
|
setScopeId(_generateScopeId());
|
||||||
logger.info(formatLogMessage(
|
observer.onScopeOpened(scopeId ?? 'NO_ID');
|
||||||
type: 'Scope',
|
observer.onDiagnostic(
|
||||||
name: scopeId ?? 'NO_ID',
|
'Scope created: ${scopeId ?? 'NO_ID'}',
|
||||||
params: {
|
details: {
|
||||||
|
'type': 'Scope',
|
||||||
|
'name': scopeId ?? 'NO_ID',
|
||||||
if (_parentScope?.scopeId != null) 'parent': _parentScope!.scopeId,
|
if (_parentScope?.scopeId != null) 'parent': _parentScope!.scopeId,
|
||||||
|
'description': 'scope created',
|
||||||
},
|
},
|
||||||
description: 'scope created',
|
);
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final Set<Module> _modulesList = HashSet();
|
final Set<Module> _modulesList = HashSet();
|
||||||
@@ -71,7 +77,7 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
/// return [Scope]
|
/// return [Scope]
|
||||||
Scope openSubScope(String name) {
|
Scope openSubScope(String name) {
|
||||||
if (!_scopeMap.containsKey(name)) {
|
if (!_scopeMap.containsKey(name)) {
|
||||||
final childScope = Scope(this, logger: logger); // Наследуем логгер вниз по иерархии
|
final childScope = Scope(this, observer: observer); // Наследуем observer вниз по иерархии
|
||||||
// print removed (trace)
|
// print removed (trace)
|
||||||
// Наследуем настройки обнаружения циклических зависимостей
|
// Наследуем настройки обнаружения циклических зависимостей
|
||||||
if (isCycleDetectionEnabled) {
|
if (isCycleDetectionEnabled) {
|
||||||
@@ -81,40 +87,44 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
childScope.enableGlobalCycleDetection();
|
childScope.enableGlobalCycleDetection();
|
||||||
}
|
}
|
||||||
_scopeMap[name] = childScope;
|
_scopeMap[name] = childScope;
|
||||||
logger.info(formatLogMessage(
|
observer.onDiagnostic(
|
||||||
type: 'SubScope',
|
'SubScope created: $name',
|
||||||
name: name,
|
details: {
|
||||||
params: {
|
'type': 'SubScope',
|
||||||
|
'name': name,
|
||||||
'id': childScope.scopeId,
|
'id': childScope.scopeId,
|
||||||
if (scopeId != null) 'parent': scopeId,
|
if (scopeId != null) 'parent': scopeId,
|
||||||
|
'description': 'subscope created',
|
||||||
},
|
},
|
||||||
description: 'subscope created',
|
);
|
||||||
));
|
|
||||||
}
|
}
|
||||||
return _scopeMap[name]!;
|
return _scopeMap[name]!;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Метод закрывает дочерний (дополнительный) [Scope].
|
/// RU: Метод закрывает дочерний (дополнительный) [Scope] асинхронно.
|
||||||
///
|
///
|
||||||
/// ENG: The method closes child (additional) [Scope].
|
/// ENG: The method closes child (additional) [Scope] asynchronously.
|
||||||
///
|
///
|
||||||
/// return [Scope]
|
/// return [Future<void>]
|
||||||
void closeSubScope(String name) {
|
Future<void> closeSubScope(String name) async {
|
||||||
final childScope = _scopeMap[name];
|
final childScope = _scopeMap[name];
|
||||||
if (childScope != null) {
|
if (childScope != null) {
|
||||||
|
await childScope.dispose(); // асинхронный вызов
|
||||||
// Очищаем детектор для дочернего скоупа
|
// Очищаем детектор для дочернего скоупа
|
||||||
if (childScope.scopeId != null) {
|
if (childScope.scopeId != null) {
|
||||||
GlobalCycleDetector.instance.removeScopeDetector(childScope.scopeId!);
|
GlobalCycleDetector.instance.removeScopeDetector(childScope.scopeId!);
|
||||||
}
|
}
|
||||||
logger.info(formatLogMessage(
|
observer.onScopeClosed(childScope.scopeId ?? name);
|
||||||
type: 'SubScope',
|
observer.onDiagnostic(
|
||||||
name: name,
|
'SubScope closed: $name',
|
||||||
params: {
|
details: {
|
||||||
|
'type': 'SubScope',
|
||||||
|
'name': name,
|
||||||
'id': childScope.scopeId,
|
'id': childScope.scopeId,
|
||||||
if (scopeId != null) 'parent': scopeId,
|
if (scopeId != null) 'parent': scopeId,
|
||||||
|
'description': 'subscope closed',
|
||||||
},
|
},
|
||||||
description: 'subscope closed',
|
);
|
||||||
));
|
|
||||||
}
|
}
|
||||||
_scopeMap.remove(name);
|
_scopeMap.remove(name);
|
||||||
}
|
}
|
||||||
@@ -126,19 +136,26 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
/// return [Scope]
|
/// return [Scope]
|
||||||
Scope installModules(List<Module> modules) {
|
Scope installModules(List<Module> modules) {
|
||||||
_modulesList.addAll(modules);
|
_modulesList.addAll(modules);
|
||||||
|
if (modules.isNotEmpty) {
|
||||||
|
observer.onModulesInstalled(
|
||||||
|
modules.map((m) => m.runtimeType.toString()).toList(),
|
||||||
|
scopeName: scopeId,
|
||||||
|
);
|
||||||
|
}
|
||||||
for (var module in modules) {
|
for (var module in modules) {
|
||||||
logger.info(formatLogMessage(
|
observer.onDiagnostic(
|
||||||
type: 'Module',
|
'Module installed: ${module.runtimeType}',
|
||||||
name: module.runtimeType.toString(),
|
details: {
|
||||||
params: {
|
'type': 'Module',
|
||||||
|
'name': module.runtimeType.toString(),
|
||||||
'scope': scopeId,
|
'scope': scopeId,
|
||||||
|
'description': 'module installed',
|
||||||
},
|
},
|
||||||
description: 'module installed',
|
);
|
||||||
));
|
|
||||||
module.builder(this);
|
module.builder(this);
|
||||||
// После builder: для всех новых биндингов
|
// После builder: для всех новых биндингов
|
||||||
for (final binding in module.bindingSet) {
|
for (final binding in module.bindingSet) {
|
||||||
binding.logger = logger;
|
binding.observer = observer;
|
||||||
binding.logAllDeferred();
|
binding.logAllDeferred();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -152,11 +169,20 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
///
|
///
|
||||||
/// return [Scope]
|
/// return [Scope]
|
||||||
Scope dropModules() {
|
Scope dropModules() {
|
||||||
logger.info(formatLogMessage(
|
if (_modulesList.isNotEmpty) {
|
||||||
type: 'Scope',
|
observer.onModulesRemoved(
|
||||||
name: scopeId,
|
_modulesList.map((m) => m.runtimeType.toString()).toList(),
|
||||||
description: 'modules dropped',
|
scopeName: scopeId,
|
||||||
));
|
);
|
||||||
|
}
|
||||||
|
observer.onDiagnostic(
|
||||||
|
'Modules dropped for scope: $scopeId',
|
||||||
|
details: {
|
||||||
|
'type': 'Scope',
|
||||||
|
'name': scopeId,
|
||||||
|
'description': 'modules dropped',
|
||||||
|
},
|
||||||
|
);
|
||||||
_modulesList.clear();
|
_modulesList.clear();
|
||||||
_rebuildResolversIndex();
|
_rebuildResolversIndex();
|
||||||
return this;
|
return this;
|
||||||
@@ -174,20 +200,17 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
/// return - returns an object of type [T] or [StateError]
|
/// return - returns an object of type [T] or [StateError]
|
||||||
///
|
///
|
||||||
T resolve<T>({String? named, dynamic params}) {
|
T resolve<T>({String? named, dynamic params}) {
|
||||||
|
observer.onInstanceRequested(T.toString(), T, scopeName: scopeId);
|
||||||
// Используем глобальное отслеживание, если включено
|
// Используем глобальное отслеживание, если включено
|
||||||
|
T result;
|
||||||
if (isGlobalCycleDetectionEnabled) {
|
if (isGlobalCycleDetectionEnabled) {
|
||||||
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) {
|
||||||
logger.error(
|
observer.onError(
|
||||||
formatLogMessage(
|
'Global cycle detection failed during resolve: $T',
|
||||||
type: 'Scope',
|
|
||||||
name: scopeId,
|
|
||||||
params: {'resolve': T.toString()},
|
|
||||||
description: 'global cycle detection failed during resolve',
|
|
||||||
),
|
|
||||||
e,
|
e,
|
||||||
s,
|
s,
|
||||||
);
|
);
|
||||||
@@ -195,21 +218,18 @@ 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(
|
observer.onError(
|
||||||
formatLogMessage(
|
'Failed to resolve: $T',
|
||||||
type: 'Scope',
|
|
||||||
name: scopeId,
|
|
||||||
params: {'resolve': T.toString()},
|
|
||||||
description: 'failed to resolve',
|
|
||||||
),
|
|
||||||
e,
|
e,
|
||||||
s,
|
s,
|
||||||
);
|
);
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_trackDisposable(result);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Разрешение с локальным детектором циклических зависимостей.
|
/// RU: Разрешение с локальным детектором циклических зависимостей.
|
||||||
@@ -218,27 +238,23 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
return withCycleDetection<T>(T, named, () {
|
return withCycleDetection<T>(T, named, () {
|
||||||
var resolved = _tryResolveInternal<T>(named: named, params: params);
|
var resolved = _tryResolveInternal<T>(named: named, params: params);
|
||||||
if (resolved != null) {
|
if (resolved != null) {
|
||||||
logger.info(formatLogMessage(
|
observer.onInstanceCreated(T.toString(), T, resolved, scopeName: scopeId);
|
||||||
type: 'Scope',
|
observer.onDiagnostic(
|
||||||
name: scopeId,
|
'Successfully resolved: $T',
|
||||||
params: {
|
details: {
|
||||||
|
'type': 'Scope',
|
||||||
|
'name': scopeId,
|
||||||
'resolve': T.toString(),
|
'resolve': T.toString(),
|
||||||
if (named != null) 'named': named,
|
if (named != null) 'named': named,
|
||||||
|
'description': 'successfully resolved',
|
||||||
},
|
},
|
||||||
description: 'successfully resolved',
|
);
|
||||||
));
|
|
||||||
return resolved;
|
return resolved;
|
||||||
} else {
|
} else {
|
||||||
logger.error(
|
observer.onError(
|
||||||
formatLogMessage(
|
'Failed to resolve: $T',
|
||||||
type: 'Scope',
|
null,
|
||||||
name: scopeId,
|
null,
|
||||||
params: {
|
|
||||||
'resolve': T.toString(),
|
|
||||||
if (named != null) 'named': named,
|
|
||||||
},
|
|
||||||
description: 'failed to resolve',
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
throw StateError(
|
throw StateError(
|
||||||
'Can\'t resolve dependency `$T`. Maybe you forget register it?');
|
'Can\'t resolve dependency `$T`. Maybe you forget register it?');
|
||||||
@@ -251,13 +267,16 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
///
|
///
|
||||||
T? tryResolve<T>({String? named, dynamic params}) {
|
T? tryResolve<T>({String? named, dynamic params}) {
|
||||||
// Используем глобальное отслеживание, если включено
|
// Используем глобальное отслеживание, если включено
|
||||||
|
T? result;
|
||||||
if (isGlobalCycleDetectionEnabled) {
|
if (isGlobalCycleDetectionEnabled) {
|
||||||
return withGlobalCycleDetection<T?>(T, named, () {
|
result = withGlobalCycleDetection<T?>(T, named, () {
|
||||||
return _tryResolveWithLocalDetection<T>(named: named, params: params);
|
return _tryResolveWithLocalDetection<T>(named: named, params: params);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return _tryResolveWithLocalDetection<T>(named: named, params: params);
|
result = _tryResolveWithLocalDetection<T>(named: named, params: params);
|
||||||
}
|
}
|
||||||
|
if (result != null) _trackDisposable(result);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Попытка разрешения с локальным детектором циклических зависимостей.
|
/// RU: Попытка разрешения с локальным детектором циклических зависимостей.
|
||||||
@@ -295,13 +314,16 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
///
|
///
|
||||||
Future<T> resolveAsync<T>({String? named, dynamic params}) async {
|
Future<T> resolveAsync<T>({String? named, dynamic params}) async {
|
||||||
// Используем глобальное отслеживание, если включено
|
// Используем глобальное отслеживание, если включено
|
||||||
|
T result;
|
||||||
if (isGlobalCycleDetectionEnabled) {
|
if (isGlobalCycleDetectionEnabled) {
|
||||||
return withGlobalCycleDetection<Future<T>>(T, named, () async {
|
result = await withGlobalCycleDetection<Future<T>>(T, named, () async {
|
||||||
return await _resolveAsyncWithLocalDetection<T>(named: named, params: params);
|
return await _resolveAsyncWithLocalDetection<T>(named: named, params: params);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return await _resolveAsyncWithLocalDetection<T>(named: named, params: params);
|
result = await _resolveAsyncWithLocalDetection<T>(named: named, params: params);
|
||||||
}
|
}
|
||||||
|
_trackDisposable(result);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Асинхронное разрешение с локальным детектором циклических зависимостей.
|
/// RU: Асинхронное разрешение с локальным детектором циклических зависимостей.
|
||||||
@@ -310,8 +332,24 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
return withCycleDetection<Future<T>>(T, named, () async {
|
return withCycleDetection<Future<T>>(T, named, () async {
|
||||||
var resolved = await _tryResolveAsyncInternal<T>(named: named, params: params);
|
var resolved = await _tryResolveAsyncInternal<T>(named: named, params: params);
|
||||||
if (resolved != null) {
|
if (resolved != null) {
|
||||||
|
observer.onInstanceCreated(T.toString(), T, resolved, scopeName: scopeId);
|
||||||
|
observer.onDiagnostic(
|
||||||
|
'Successfully async resolved: $T',
|
||||||
|
details: {
|
||||||
|
'type': 'Scope',
|
||||||
|
'name': scopeId,
|
||||||
|
'resolve': T.toString(),
|
||||||
|
if (named != null) 'named': named,
|
||||||
|
'description': 'successfully resolved (async)',
|
||||||
|
},
|
||||||
|
);
|
||||||
return resolved;
|
return resolved;
|
||||||
} else {
|
} else {
|
||||||
|
observer.onError(
|
||||||
|
'Failed to async resolve: $T',
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
);
|
||||||
throw StateError(
|
throw StateError(
|
||||||
'Can\'t resolve async dependency `$T`. Maybe you forget register it?');
|
'Can\'t resolve async dependency `$T`. Maybe you forget register it?');
|
||||||
}
|
}
|
||||||
@@ -320,13 +358,16 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
|
|
||||||
Future<T?> tryResolveAsync<T>({String? named, dynamic params}) async {
|
Future<T?> tryResolveAsync<T>({String? named, dynamic params}) async {
|
||||||
// Используем глобальное отслеживание, если включено
|
// Используем глобальное отслеживание, если включено
|
||||||
|
T? result;
|
||||||
if (isGlobalCycleDetectionEnabled) {
|
if (isGlobalCycleDetectionEnabled) {
|
||||||
return withGlobalCycleDetection<Future<T?>>(T, named, () async {
|
result = await withGlobalCycleDetection<Future<T?>>(T, named, () async {
|
||||||
return await _tryResolveAsyncWithLocalDetection<T>(named: named, params: params);
|
return await _tryResolveAsyncWithLocalDetection<T>(named: named, params: params);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return await _tryResolveAsyncWithLocalDetection<T>(named: named, params: params);
|
result = await _tryResolveAsyncWithLocalDetection<T>(named: named, params: params);
|
||||||
}
|
}
|
||||||
|
if (result != null) _trackDisposable(result);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RU: Асинхронная попытка разрешения с локальным детектором циклических зависимостей.
|
/// RU: Асинхронная попытка разрешения с локальным детектором циклических зависимостей.
|
||||||
@@ -366,4 +407,27 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// INTERNAL: Tracks Disposable objects
|
||||||
|
void _trackDisposable(Object? obj) {
|
||||||
|
if (obj is Disposable && !_disposables.contains(obj)) {
|
||||||
|
_disposables.add(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calls dispose on all tracked disposables and child scopes recursively (async).
|
||||||
|
Future<void> dispose() async {
|
||||||
|
// First dispose children scopes
|
||||||
|
for (final subScope in _scopeMap.values) {
|
||||||
|
await subScope.dispose();
|
||||||
|
}
|
||||||
|
_scopeMap.clear();
|
||||||
|
// Then dispose own disposables
|
||||||
|
for (final d in _disposables) {
|
||||||
|
try {
|
||||||
|
await d.dispose();
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
_disposables.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
name: cherrypick
|
name: cherrypick
|
||||||
description: Cherrypick is a small dependency injection (DI) library for dart/flutter projects.
|
description: Cherrypick is a small dependency injection (DI) library for dart/flutter projects.
|
||||||
version: 3.0.0-dev.6
|
version: 3.0.0-dev.8
|
||||||
homepage: https://pese-git.github.io/cherrypick-site/
|
homepage: https://pese-git.github.io/cherrypick-site/
|
||||||
documentation: https://github.com/pese-git/cherrypick/wiki
|
documentation: https://github.com/pese-git/cherrypick/wiki
|
||||||
repository: https://github.com/pese-git/cherrypick
|
repository: https://github.com/pese-git/cherrypick
|
||||||
|
|||||||
@@ -23,38 +23,27 @@ class CyclicModule extends Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
late MockLogger logger;
|
late MockObserver observer;
|
||||||
|
|
||||||
setUp(() {
|
setUp(() {
|
||||||
logger = MockLogger();
|
observer = MockObserver();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Global logger receives Scope and Binding events', () {
|
test('Global logger receives Binding events', () {
|
||||||
final scope = Scope(null, logger: logger);
|
final scope = Scope(null, observer: observer);
|
||||||
scope.installModules([DummyModule()]);
|
scope.installModules([DummyModule()]);
|
||||||
final _ = scope.resolve<DummyService>(named: 'test');
|
final _ = scope.resolve<DummyService>(named: 'test');
|
||||||
|
|
||||||
// Новый стиль проверки для formatLogMessage:
|
// Проверяем, что биндинг DummyService зарегистрирован
|
||||||
expect(
|
expect(
|
||||||
logger.infos.any((m) => m.startsWith('[Scope:') && m.contains('created')),
|
observer.bindings.any((m) => m.contains('DummyService')),
|
||||||
isTrue,
|
|
||||||
);
|
|
||||||
expect(
|
|
||||||
logger.infos.any((m) => m.startsWith('[Binding:DummyService') && m.contains('created')),
|
|
||||||
isTrue,
|
|
||||||
);
|
|
||||||
expect(
|
|
||||||
logger.infos.any((m) => m.startsWith('[Binding:DummyService') && m.contains('named') && m.contains('name=test')),
|
|
||||||
isTrue,
|
|
||||||
);
|
|
||||||
expect(
|
|
||||||
logger.infos.any((m) => m.startsWith('[Scope:') && m.contains('resolve=DummyService') && m.contains('successfully resolved')),
|
|
||||||
isTrue,
|
isTrue,
|
||||||
);
|
);
|
||||||
|
// Можно добавить проверки diagnostics, если Scope что-то пишет туда
|
||||||
});
|
});
|
||||||
|
|
||||||
test('CycleDetector logs cycle detection error', () {
|
test('CycleDetector logs cycle detection error', () {
|
||||||
final scope = Scope(null, logger: logger);
|
final scope = Scope(null, observer: observer);
|
||||||
// print('[DEBUG] TEST SCOPE logger type=${scope.logger.runtimeType} hash=${scope.logger.hashCode}');
|
// print('[DEBUG] TEST SCOPE logger type=${scope.logger.runtimeType} hash=${scope.logger.hashCode}');
|
||||||
scope.enableCycleDetection();
|
scope.enableCycleDetection();
|
||||||
scope.installModules([CyclicModule()]);
|
scope.installModules([CyclicModule()]);
|
||||||
@@ -62,12 +51,11 @@ void main() {
|
|||||||
() => scope.resolve<A>(),
|
() => scope.resolve<A>(),
|
||||||
throwsA(isA<CircularDependencyException>()),
|
throwsA(isA<CircularDependencyException>()),
|
||||||
);
|
);
|
||||||
// Дополнительно ищем и среди info на случай если лог от CycleDetector ошибочно не попал в errors
|
// Проверяем, что цикл зафиксирован либо в errors, либо в diagnostics либо cycles
|
||||||
final foundInErrors = logger.errors.any((m) =>
|
final foundInErrors = observer.errors.any((m) => m.contains('cycle detected'));
|
||||||
m.startsWith('[CycleDetector:') && m.contains('cycle detected'));
|
final foundInDiagnostics = observer.diagnostics.any((m) => m.contains('cycle detected'));
|
||||||
final foundInInfos = logger.infos.any((m) =>
|
final foundCycleNotified = observer.cycles.isNotEmpty;
|
||||||
m.startsWith('[CycleDetector:') && m.contains('cycle detected'));
|
expect(foundInErrors || foundInDiagnostics || foundCycleNotified, isTrue,
|
||||||
expect(foundInErrors || foundInInfos, isTrue,
|
reason: 'Ожидаем хотя бы один лог о цикле! errors: ${observer.errors}\ndiag: ${observer.diagnostics}\ncycles: ${observer.cycles}');
|
||||||
reason: 'Ожидаем хотя бы один лог о цикле на уровне error или info; вот все errors: ${logger.errors}\ninfos: ${logger.infos}');
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1,16 +1,48 @@
|
|||||||
import 'package:cherrypick/cherrypick.dart';
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
|
||||||
class MockLogger implements CherryPickLogger {
|
class MockObserver implements CherryPickObserver {
|
||||||
final List<String> infos = [];
|
final List<String> diagnostics = [];
|
||||||
final List<String> warns = [];
|
final List<String> warnings = [];
|
||||||
final List<String> errors = [];
|
final List<String> errors = [];
|
||||||
|
final List<List<String>> cycles = [];
|
||||||
|
final List<String> bindings = [];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void info(String message) => infos.add(message);
|
void onDiagnostic(String message, {Object? details}) =>
|
||||||
|
diagnostics.add(message);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void warn(String message) => warns.add(message);
|
void onWarning(String message, {Object? details}) => warnings.add(message);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void error(String message, [Object? e, StackTrace? s]) =>
|
void onError(String message, Object? error, StackTrace? stackTrace) =>
|
||||||
errors.add(
|
errors.add(
|
||||||
'$message${e != null ? ' $e' : ''}${s != null ? '\n$s' : ''}');
|
'$message${error != null ? ' $error' : ''}${stackTrace != null ? '\n$stackTrace' : ''}');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onCycleDetected(List<String> chain, {String? scopeName}) =>
|
||||||
|
cycles.add(chain);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onBindingRegistered(String name, Type type, {String? scopeName}) =>
|
||||||
|
bindings.add('$name $type');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInstanceRequested(String name, Type type, {String? scopeName}) {}
|
||||||
|
@override
|
||||||
|
void onInstanceCreated(String name, Type type, Object instance, {String? scopeName}) {}
|
||||||
|
@override
|
||||||
|
void onInstanceDisposed(String name, Type type, Object instance, {String? scopeName}) {}
|
||||||
|
@override
|
||||||
|
void onModulesInstalled(List<String> moduleNames, {String? scopeName}) {}
|
||||||
|
@override
|
||||||
|
void onModulesRemoved(List<String> moduleNames, {String? scopeName}) {}
|
||||||
|
@override
|
||||||
|
void onScopeOpened(String name) {}
|
||||||
|
@override
|
||||||
|
void onScopeClosed(String name) {}
|
||||||
|
@override
|
||||||
|
void onCacheHit(String name, Type type, {String? scopeName}) {}
|
||||||
|
@override
|
||||||
|
void onCacheMiss(String name, Type type, {String? scopeName}) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,16 +4,16 @@ import 'package:cherrypick/cherrypick.dart';
|
|||||||
import '../mock_logger.dart';
|
import '../mock_logger.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
late MockLogger logger;
|
late MockObserver observer;
|
||||||
setUp(() {
|
setUp(() {
|
||||||
logger = MockLogger();
|
observer = MockObserver();
|
||||||
CherryPick.setGlobalLogger(logger);
|
CherryPick.setGlobalObserver(observer);
|
||||||
});
|
});
|
||||||
group('CycleDetector', () {
|
group('CycleDetector', () {
|
||||||
late CycleDetector detector;
|
late CycleDetector detector;
|
||||||
|
|
||||||
setUp(() {
|
setUp(() {
|
||||||
detector = CycleDetector(logger: logger);
|
detector = CycleDetector(observer: observer);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should detect simple circular dependency', () {
|
test('should detect simple circular dependency', () {
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import 'package:test/test.dart';
|
|||||||
import '../mock_logger.dart';
|
import '../mock_logger.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
late MockLogger logger;
|
late MockObserver observer;
|
||||||
setUp(() {
|
setUp(() {
|
||||||
logger = MockLogger();
|
observer = MockObserver();
|
||||||
CherryPick.setGlobalLogger(logger);
|
CherryPick.setGlobalObserver(observer);
|
||||||
});
|
});
|
||||||
group('CherryPick Cycle Detection Helper Methods', () {
|
group('CherryPick Cycle Detection Helper Methods', () {
|
||||||
setUp(() {
|
setUp(() {
|
||||||
|
|||||||
@@ -1,26 +1,138 @@
|
|||||||
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', () {
|
||||||
test('Scope has no parent if constructed with null', () {
|
test('Scope has no parent if constructed with null', () {
|
||||||
final logger = MockLogger();
|
final observer = MockObserver();
|
||||||
final scope = Scope(null, logger: logger);
|
final scope = Scope(null, observer: observer);
|
||||||
expect(scope.parentScope, null);
|
expect(scope.parentScope, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Can open and retrieve the same subScope by key', () {
|
test('Can open and retrieve the same subScope by key', () {
|
||||||
final logger = MockLogger();
|
final observer = MockObserver();
|
||||||
final scope = Scope(null, logger: logger);
|
final scope = Scope(null, observer: observer);
|
||||||
expect(Scope(scope, logger: logger), isNotNull); // эквивалент
|
expect(Scope(scope, observer: observer), isNotNull); // эквивалент
|
||||||
|
});
|
||||||
|
test('closeSubScope removes subscope so next openSubScope returns new', () async {
|
||||||
|
final observer = MockObserver();
|
||||||
|
final scope = Scope(null, observer: observer);
|
||||||
|
final subScope = scope.openSubScope("child");
|
||||||
|
expect(scope.openSubScope("child"), same(subScope));
|
||||||
|
await scope.closeSubScope("child");
|
||||||
|
final newSubScope = scope.openSubScope("child");
|
||||||
|
expect(newSubScope, isNot(same(subScope)));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('closeSubScope removes subscope so next openSubScope returns new', () {
|
test('closeSubScope removes subscope so next openSubScope returns new', () {
|
||||||
final logger = MockLogger();
|
final observer = MockObserver();
|
||||||
final scope = Scope(null, logger: logger);
|
final scope = Scope(null, observer: observer);
|
||||||
expect(Scope(scope, logger: logger), isNotNull); // эквивалент
|
expect(Scope(scope, observer: observer), isNotNull); // эквивалент
|
||||||
// Нет необходимости тестировать open/closeSubScope в этом юните
|
// Нет необходимости тестировать open/closeSubScope в этом юните
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -28,54 +140,48 @@ void main() {
|
|||||||
// --------------------------------------------------------------------------
|
// --------------------------------------------------------------------------
|
||||||
group('Dependency Resolution (standard)', () {
|
group('Dependency Resolution (standard)', () {
|
||||||
test("Throws StateError if value can't be resolved", () {
|
test("Throws StateError if value can't be resolved", () {
|
||||||
final logger = MockLogger();
|
final observer = MockObserver();
|
||||||
final scope = Scope(null, logger: logger);
|
final scope = Scope(null, observer: observer);
|
||||||
expect(() => scope.resolve<String>(), throwsA(isA<StateError>()));
|
expect(() => scope.resolve<String>(), throwsA(isA<StateError>()));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Resolves value after adding a dependency', () {
|
test('Resolves value after adding a dependency', () {
|
||||||
final logger = MockLogger();
|
final observer = MockObserver();
|
||||||
final expectedValue = 'test string';
|
final expectedValue = 'test string';
|
||||||
final scope = Scope(null, logger: logger)
|
final scope = Scope(null, observer: observer)
|
||||||
.installModules([TestModule<String>(value: expectedValue)]);
|
.installModules([TestModule<String>(value: expectedValue)]);
|
||||||
expect(scope.resolve<String>(), expectedValue);
|
expect(scope.resolve<String>(), expectedValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Returns a value from parent scope', () {
|
test('Returns a value from parent scope', () {
|
||||||
final logger = MockLogger();
|
final observer = MockObserver();
|
||||||
final expectedValue = 5;
|
final expectedValue = 5;
|
||||||
final parentScope = Scope(null, logger: logger);
|
final parentScope = Scope(null, observer: observer);
|
||||||
final scope = Scope(parentScope, logger: logger);
|
final scope = Scope(parentScope, observer: observer);
|
||||||
|
|
||||||
parentScope.installModules([TestModule<int>(value: expectedValue)]);
|
parentScope.installModules([TestModule<int>(value: expectedValue)]);
|
||||||
|
|
||||||
expect(scope.resolve<int>(), expectedValue);
|
expect(scope.resolve<int>(), expectedValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Returns several values from parent container', () {
|
test('Returns several values from parent container', () {
|
||||||
final logger = MockLogger();
|
final observer = MockObserver();
|
||||||
final expectedIntValue = 5;
|
final expectedIntValue = 5;
|
||||||
final expectedStringValue = 'Hello world';
|
final expectedStringValue = 'Hello world';
|
||||||
final parentScope = Scope(null, logger: logger).installModules([
|
final parentScope = Scope(null, observer: observer).installModules([
|
||||||
TestModule<int>(value: expectedIntValue),
|
TestModule<int>(value: expectedIntValue),
|
||||||
TestModule<String>(value: expectedStringValue)
|
TestModule<String>(value: expectedStringValue)
|
||||||
]);
|
]);
|
||||||
final scope = Scope(parentScope, logger: logger);
|
final scope = Scope(parentScope, observer: observer);
|
||||||
|
|
||||||
expect(scope.resolve<int>(), expectedIntValue);
|
expect(scope.resolve<int>(), expectedIntValue);
|
||||||
expect(scope.resolve<String>(), expectedStringValue);
|
expect(scope.resolve<String>(), expectedStringValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Throws StateError if parent hasn't value too", () {
|
test("Throws StateError if parent hasn't value too", () {
|
||||||
final logger = MockLogger();
|
final observer = MockObserver();
|
||||||
final parentScope = Scope(null, logger: logger);
|
final parentScope = Scope(null, observer: observer);
|
||||||
final scope = Scope(parentScope, logger: logger);
|
final scope = Scope(parentScope, observer: observer);
|
||||||
expect(() => scope.resolve<int>(), throwsA(isA<StateError>()));
|
expect(() => scope.resolve<int>(), throwsA(isA<StateError>()));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("After dropModules resolves fail", () {
|
test("After dropModules resolves fail", () {
|
||||||
final logger = MockLogger();
|
final observer = MockObserver();
|
||||||
final scope = Scope(null, logger: logger)..installModules([TestModule<int>(value: 5)]);
|
final scope = Scope(null, observer: observer)..installModules([TestModule<int>(value: 5)]);
|
||||||
expect(scope.resolve<int>(), 5);
|
expect(scope.resolve<int>(), 5);
|
||||||
scope.dropModules();
|
scope.dropModules();
|
||||||
expect(() => scope.resolve<int>(), throwsA(isA<StateError>()));
|
expect(() => scope.resolve<int>(), throwsA(isA<StateError>()));
|
||||||
@@ -85,8 +191,8 @@ void main() {
|
|||||||
// --------------------------------------------------------------------------
|
// --------------------------------------------------------------------------
|
||||||
group('Named Dependencies', () {
|
group('Named Dependencies', () {
|
||||||
test('Resolve named binding', () {
|
test('Resolve named binding', () {
|
||||||
final logger = MockLogger();
|
final observer = MockObserver();
|
||||||
final scope = Scope(null, logger: logger)
|
final scope = Scope(null, observer: observer)
|
||||||
..installModules([
|
..installModules([
|
||||||
TestModule<String>(value: "first"),
|
TestModule<String>(value: "first"),
|
||||||
TestModule<String>(value: "second", name: "special")
|
TestModule<String>(value: "second", name: "special")
|
||||||
@@ -94,20 +200,18 @@ void main() {
|
|||||||
expect(scope.resolve<String>(named: "special"), "second");
|
expect(scope.resolve<String>(named: "special"), "second");
|
||||||
expect(scope.resolve<String>(), "first");
|
expect(scope.resolve<String>(), "first");
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Named binding does not clash with unnamed', () {
|
test('Named binding does not clash with unnamed', () {
|
||||||
final logger = MockLogger();
|
final observer = MockObserver();
|
||||||
final scope = Scope(null, logger: logger)
|
final scope = Scope(null, observer: observer)
|
||||||
..installModules([
|
..installModules([
|
||||||
TestModule<String>(value: "foo", name: "bar"),
|
TestModule<String>(value: "foo", name: "bar"),
|
||||||
]);
|
]);
|
||||||
expect(() => scope.resolve<String>(), throwsA(isA<StateError>()));
|
expect(() => scope.resolve<String>(), throwsA(isA<StateError>()));
|
||||||
expect(scope.resolve<String>(named: "bar"), "foo");
|
expect(scope.resolve<String>(named: "bar"), "foo");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("tryResolve returns null for missing named", () {
|
test("tryResolve returns null for missing named", () {
|
||||||
final logger = MockLogger();
|
final observer = MockObserver();
|
||||||
final scope = Scope(null, logger: logger)
|
final scope = Scope(null, observer: observer)
|
||||||
..installModules([
|
..installModules([
|
||||||
TestModule<String>(value: "foo"),
|
TestModule<String>(value: "foo"),
|
||||||
]);
|
]);
|
||||||
@@ -118,8 +222,8 @@ void main() {
|
|||||||
// --------------------------------------------------------------------------
|
// --------------------------------------------------------------------------
|
||||||
group('Provider with parameters', () {
|
group('Provider with parameters', () {
|
||||||
test('Resolve dependency using providerWithParams', () {
|
test('Resolve dependency using providerWithParams', () {
|
||||||
final logger = MockLogger();
|
final observer = MockObserver();
|
||||||
final scope = Scope(null, logger: logger)
|
final scope = Scope(null, observer: observer)
|
||||||
..installModules([
|
..installModules([
|
||||||
_InlineModule((m, s) {
|
_InlineModule((m, s) {
|
||||||
m.bind<int>().toProvideWithParams((param) => (param as int) * 2);
|
m.bind<int>().toProvideWithParams((param) => (param as int) * 2);
|
||||||
@@ -133,8 +237,8 @@ void main() {
|
|||||||
// --------------------------------------------------------------------------
|
// --------------------------------------------------------------------------
|
||||||
group('Async Resolution', () {
|
group('Async Resolution', () {
|
||||||
test('Resolve async instance', () async {
|
test('Resolve async instance', () async {
|
||||||
final logger = MockLogger();
|
final observer = MockObserver();
|
||||||
final scope = Scope(null, logger: logger)
|
final scope = Scope(null, observer: observer)
|
||||||
..installModules([
|
..installModules([
|
||||||
_InlineModule((m, s) {
|
_InlineModule((m, s) {
|
||||||
m.bind<String>().toInstance(Future.value('async value'));
|
m.bind<String>().toInstance(Future.value('async value'));
|
||||||
@@ -142,10 +246,9 @@ void main() {
|
|||||||
]);
|
]);
|
||||||
expect(await scope.resolveAsync<String>(), "async value");
|
expect(await scope.resolveAsync<String>(), "async value");
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Resolve async provider', () async {
|
test('Resolve async provider', () async {
|
||||||
final logger = MockLogger();
|
final observer = MockObserver();
|
||||||
final scope = Scope(null, logger: logger)
|
final scope = Scope(null, observer: observer)
|
||||||
..installModules([
|
..installModules([
|
||||||
_InlineModule((m, s) {
|
_InlineModule((m, s) {
|
||||||
m.bind<int>().toProvide(() async => 7);
|
m.bind<int>().toProvide(() async => 7);
|
||||||
@@ -153,10 +256,9 @@ void main() {
|
|||||||
]);
|
]);
|
||||||
expect(await scope.resolveAsync<int>(), 7);
|
expect(await scope.resolveAsync<int>(), 7);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Resolve async provider with param', () async {
|
test('Resolve async provider with param', () async {
|
||||||
final logger = MockLogger();
|
final observer = MockObserver();
|
||||||
final scope = Scope(null, logger: logger)
|
final scope = Scope(null, observer: observer)
|
||||||
..installModules([
|
..installModules([
|
||||||
_InlineModule((m, s) {
|
_InlineModule((m, s) {
|
||||||
m.bind<int>().toProvideWithParams((x) async => (x as int) * 3);
|
m.bind<int>().toProvideWithParams((x) async => (x as int) * 3);
|
||||||
@@ -165,10 +267,9 @@ void main() {
|
|||||||
expect(await scope.resolveAsync<int>(params: 2), 6);
|
expect(await scope.resolveAsync<int>(params: 2), 6);
|
||||||
expect(() => scope.resolveAsync<int>(), throwsA(isA<StateError>()));
|
expect(() => scope.resolveAsync<int>(), throwsA(isA<StateError>()));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('tryResolveAsync returns null for missing', () async {
|
test('tryResolveAsync returns null for missing', () async {
|
||||||
final logger = MockLogger();
|
final observer = MockObserver();
|
||||||
final scope = Scope(null, logger: logger);
|
final scope = Scope(null, observer: observer);
|
||||||
final result = await scope.tryResolveAsync<String>();
|
final result = await scope.tryResolveAsync<String>();
|
||||||
expect(result, isNull);
|
expect(result, isNull);
|
||||||
});
|
});
|
||||||
@@ -177,46 +278,90 @@ void main() {
|
|||||||
// --------------------------------------------------------------------------
|
// --------------------------------------------------------------------------
|
||||||
group('Optional resolution and error handling', () {
|
group('Optional resolution and error handling', () {
|
||||||
test("tryResolve returns null for missing dependency", () {
|
test("tryResolve returns null for missing dependency", () {
|
||||||
final logger = MockLogger();
|
final observer = MockObserver();
|
||||||
final scope = Scope(null, logger: logger);
|
final scope = Scope(null, observer: observer);
|
||||||
expect(scope.tryResolve<int>(), isNull);
|
expect(scope.tryResolve<int>(), isNull);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Не реализован:
|
|
||||||
// test("Container bind() throws state error (if it's parent already has a resolver)", () {
|
|
||||||
// final parentScope = new Scope(null).installModules([TestModule<String>(value: "string one")]);
|
|
||||||
// final scope = new Scope(parentScope);
|
|
||||||
|
|
||||||
// expect(
|
|
||||||
// () => scope.installModules([TestModule<String>(value: "string two")]),
|
|
||||||
// throwsA(isA<StateError>()));
|
|
||||||
// });
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// --------------------------------------------------------------------------
|
||||||
// Вспомогательные модули
|
group('Disposable resource management', () {
|
||||||
|
test('scope.disposeAsync calls dispose on singleton disposable', () async {
|
||||||
|
final scope = CherryPick.openRootScope();
|
||||||
|
scope.installModules([ModuleWithDisposable()]);
|
||||||
|
final t = scope.resolve<TestDisposable>();
|
||||||
|
expect(t.disposed, isFalse);
|
||||||
|
await scope.dispose();
|
||||||
|
expect(t.disposed, isTrue);
|
||||||
|
});
|
||||||
|
test('scope.disposeAsync calls dispose on all unique disposables', () async {
|
||||||
|
final scope = Scope(null, observer: MockObserver());
|
||||||
|
scope.installModules([ModuleWithDisposable()]);
|
||||||
|
final t1 = scope.resolve<TestDisposable>();
|
||||||
|
final t2 = scope.resolve<AnotherDisposable>();
|
||||||
|
expect(t1.disposed, isFalse);
|
||||||
|
expect(t2.disposed, isFalse);
|
||||||
|
await scope.dispose();
|
||||||
|
expect(t1.disposed, isTrue);
|
||||||
|
expect(t2.disposed, isTrue);
|
||||||
|
});
|
||||||
|
test('calling disposeAsync twice does not throw and not call twice', () async {
|
||||||
|
final scope = CherryPick.openRootScope();
|
||||||
|
scope.installModules([ModuleWithDisposable()]);
|
||||||
|
final t = scope.resolve<TestDisposable>();
|
||||||
|
await scope.dispose();
|
||||||
|
await scope.dispose();
|
||||||
|
expect(t.disposed, isTrue);
|
||||||
|
});
|
||||||
|
test('Non-disposable dependency is ignored by scope.disposeAsync', () async {
|
||||||
|
final scope = CherryPick.openRootScope();
|
||||||
|
scope.installModules([ModuleWithDisposable()]);
|
||||||
|
final s = scope.resolve<String>();
|
||||||
|
expect(s, 'super string');
|
||||||
|
await scope.dispose();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
class TestModule<T> extends Module {
|
// --------------------------------------------------------------------------
|
||||||
final T value;
|
// Расширенные edge-тесты для dispose и subScope
|
||||||
final String? name;
|
group('Scope/subScope dispose edge cases', () {
|
||||||
|
test('Dispose called in closed subScope only', () async {
|
||||||
|
final root = CherryPick.openRootScope();
|
||||||
|
final sub = root.openSubScope('feature')..installModules([ModuleCountingDisposable()]);
|
||||||
|
final d = sub.resolve<CountingDisposable>();
|
||||||
|
expect(d.disposeCount, 0);
|
||||||
|
|
||||||
TestModule({required this.value, this.name});
|
await root.closeSubScope('feature');
|
||||||
@override
|
expect(d.disposeCount, 1); // dispose должен быть вызван
|
||||||
void builder(Scope currentScope) {
|
|
||||||
if (name == null) {
|
|
||||||
bind<T>().toInstance(value);
|
|
||||||
} else {
|
|
||||||
bind<T>().withName(name!).toInstance(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Вспомогательный модуль для подстановки builder'а через конструктор
|
// Повторное закрытие не вызывает double-dispose
|
||||||
class _InlineModule extends Module {
|
await root.closeSubScope('feature');
|
||||||
final void Function(Module, Scope) _builder;
|
expect(d.disposeCount, 1);
|
||||||
_InlineModule(this._builder);
|
|
||||||
|
|
||||||
@override
|
// Повторное открытие subScope создает NEW instance (dispose на старый не вызовется снова)
|
||||||
void builder(Scope s) => _builder(this, s);
|
final sub2 = root.openSubScope('feature')..installModules([ModuleCountingDisposable()]);
|
||||||
}
|
final d2 = sub2.resolve<CountingDisposable>();
|
||||||
|
expect(identical(d, d2), isFalse);
|
||||||
|
await root.closeSubScope('feature');
|
||||||
|
expect(d2.disposeCount, 1);
|
||||||
|
});
|
||||||
|
test('Dispose for all nested subScopes on root disposeAsync', () async {
|
||||||
|
final root = CherryPick.openRootScope();
|
||||||
|
root.openSubScope('a').openSubScope('b').installModules([ModuleCountingDisposable()]);
|
||||||
|
final d = root.openSubScope('a').openSubScope('b').resolve<CountingDisposable>();
|
||||||
|
await root.dispose();
|
||||||
|
expect(d.disposeCount, 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
group('Async disposable (Future test)', () {
|
||||||
|
test('Async Disposable is awaited on disposeAsync', () async {
|
||||||
|
final scope = CherryPick.openRootScope()..installModules([AsyncExampleModule()]);
|
||||||
|
final d = scope.resolve<AsyncExampleDisposable>();
|
||||||
|
expect(d.disposed, false);
|
||||||
|
await scope.dispose();
|
||||||
|
expect(d.disposed, true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
## 1.1.1
|
||||||
|
|
||||||
|
- **FIX**(license): correct urls.
|
||||||
|
|
||||||
## 1.1.0
|
## 1.1.0
|
||||||
|
|
||||||
- Graduate package to a stable release. See pre-releases prior to this version for changelog entries.
|
- Graduate package to a stable release. See pre-releases prior to this version for changelog entries.
|
||||||
|
|||||||
@@ -192,7 +192,7 @@
|
|||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ library;
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -1,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
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
|
## 1.1.3-dev.8
|
||||||
|
|
||||||
|
- Update a dependency to the latest release.
|
||||||
|
|
||||||
|
## 1.1.3-dev.7
|
||||||
|
|
||||||
|
- **FIX**(license): correct urls.
|
||||||
|
|
||||||
## 1.1.3-dev.6
|
## 1.1.3-dev.6
|
||||||
|
|
||||||
- Update a dependency to the latest release.
|
- Update a dependency to the latest release.
|
||||||
|
|||||||
@@ -192,7 +192,7 @@
|
|||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
|||||||
@@ -94,4 +94,4 @@ Contributions to improve this library are welcome. Feel free to open issues and
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is licensed under the Apache License 2.0. A copy of the license can be obtained at [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0).
|
This project is licensed under the Apache License 2.0. A copy of the license can be obtained at [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0).
|
||||||
@@ -4,7 +4,7 @@ library;
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import 'package:flutter/widgets.dart';
|
|||||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
/// you may not use this file except in compliance with the License.
|
/// you may not use this file except in compliance with the License.
|
||||||
/// You may obtain a copy of the License at
|
/// You may obtain a copy of the License at
|
||||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
/// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
/// Unless required by applicable law or agreed to in writing, software
|
/// Unless required by applicable law or agreed to in writing, software
|
||||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
name: cherrypick_flutter
|
name: cherrypick_flutter
|
||||||
description: "Flutter library that allows access to the root scope through the context using `CherryPickProvider`."
|
description: "Flutter library that allows access to the root scope through the context using `CherryPickProvider`."
|
||||||
version: 1.1.3-dev.6
|
version: 1.1.3-dev.8
|
||||||
homepage: https://pese-git.github.io/cherrypick-site/
|
homepage: https://pese-git.github.io/cherrypick-site/
|
||||||
documentation: https://github.com/pese-git/cherrypick/wiki
|
documentation: https://github.com/pese-git/cherrypick/wiki
|
||||||
repository: https://github.com/pese-git/cherrypick
|
repository: https://github.com/pese-git/cherrypick
|
||||||
@@ -19,7 +19,7 @@ environment:
|
|||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
cherrypick: ^3.0.0-dev.6
|
cherrypick: ^3.0.0-dev.8
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
## 1.1.1
|
||||||
|
|
||||||
|
- **FIX**(license): correct urls.
|
||||||
|
|
||||||
## 1.1.0
|
## 1.1.0
|
||||||
|
|
||||||
- Graduate package to a stable release. See pre-releases prior to this version for changelog entries.
|
- Graduate package to a stable release. See pre-releases prior to this version for changelog entries.
|
||||||
|
|||||||
@@ -192,7 +192,7 @@
|
|||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ library;
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -2,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
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|||||||
@@ -185,6 +185,41 @@ final service = scope.tryResolve<OptionalService>(); // returns null if not exis
|
|||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
## Automatic resource management: Disposable and dispose
|
||||||
|
|
||||||
|
CherryPick makes it easy to clean up resources for your singleton services and other objects registered in DI.
|
||||||
|
If your class implements the `Disposable` interface, always **await** `scope.dispose()` (or `CherryPick.closeRootScope()`) when you want to free all resources in your scope — CherryPick will automatically await `dispose()` for every object that implements `Disposable` and was resolved via DI.
|
||||||
|
This ensures safe and graceful resource management (including any async resource cleanup: streams, DB connections, sockets, etc.).
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class LoggingService implements Disposable {
|
||||||
|
@override
|
||||||
|
FutureOr<void> dispose() async {
|
||||||
|
// Close files, streams, and perform async cleanup here.
|
||||||
|
print('LoggingService disposed!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> main() async {
|
||||||
|
final scope = openRootScope();
|
||||||
|
scope.installModules([
|
||||||
|
_LoggingModule(),
|
||||||
|
]);
|
||||||
|
final logger = scope.resolve<LoggingService>();
|
||||||
|
// Use logger...
|
||||||
|
await scope.dispose(); // prints: LoggingService disposed!
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LoggingModule extends Module {
|
||||||
|
@override
|
||||||
|
void builder(Scope scope) {
|
||||||
|
bind<LoggingService>().toProvide(() => LoggingService()).singleton();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Dependency injection with annotations & code generation
|
## Dependency injection with annotations & code generation
|
||||||
|
|
||||||
CherryPick supports DI with annotations, letting you eliminate manual DI setup.
|
CherryPick supports DI with annotations, letting you eliminate manual DI setup.
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Логирование
|
## Логирование
|
||||||
|
|||||||
@@ -127,28 +127,28 @@ packages:
|
|||||||
path: "../../cherrypick"
|
path: "../../cherrypick"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "3.0.0-dev.3"
|
version: "3.0.0-dev.7"
|
||||||
cherrypick_annotations:
|
cherrypick_annotations:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "../../cherrypick_annotations"
|
path: "../../cherrypick_annotations"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "1.1.0"
|
version: "1.1.1"
|
||||||
cherrypick_flutter:
|
cherrypick_flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "../../cherrypick_flutter"
|
path: "../../cherrypick_flutter"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "1.1.3-dev.3"
|
version: "1.1.3-dev.7"
|
||||||
cherrypick_generator:
|
cherrypick_generator:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
path: "../../cherrypick_generator"
|
path: "../../cherrypick_generator"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "1.1.0"
|
version: "1.1.1"
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import 'package:cherrypick/cherrypick.dart';
|
|||||||
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:talker_flutter/talker_flutter.dart';
|
||||||
|
|
||||||
|
|
||||||
import 'domain/repository/post_repository.dart';
|
import 'domain/repository/post_repository.dart';
|
||||||
import 'presentation/bloc/post_bloc.dart';
|
import 'presentation/bloc/post_bloc.dart';
|
||||||
@@ -9,26 +11,38 @@ import 'router/app_router.dart';
|
|||||||
|
|
||||||
part 'app.inject.cherrypick.g.dart';
|
part 'app.inject.cherrypick.g.dart';
|
||||||
|
|
||||||
|
class TalkerProvider extends InheritedWidget {
|
||||||
|
final Talker talker;
|
||||||
|
const TalkerProvider({required this.talker, required super.child, super.key});
|
||||||
|
static Talker of(BuildContext context) => context.dependOnInheritedWidgetOfExactType<TalkerProvider>()!.talker;
|
||||||
|
@override
|
||||||
|
bool updateShouldNotify(TalkerProvider oldWidget) => oldWidget.talker != talker;
|
||||||
|
}
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
class MyApp extends StatelessWidget with _$MyApp {
|
class MyApp extends StatelessWidget with _$MyApp {
|
||||||
final _appRouter = AppRouter();
|
final _appRouter = AppRouter();
|
||||||
|
final Talker talker;
|
||||||
|
|
||||||
@named('repo')
|
@named('repo')
|
||||||
@inject()
|
@inject()
|
||||||
late final PostRepository repository;
|
late final PostRepository repository;
|
||||||
|
|
||||||
MyApp({super.key}) {
|
MyApp({super.key, required this.talker}) {
|
||||||
_inject(this);
|
_inject(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return TalkerProvider(
|
||||||
create: (_) => PostBloc(repository),
|
talker: talker,
|
||||||
child: MaterialApp.router(
|
child: BlocProvider(
|
||||||
routeInformationParser: _appRouter.defaultRouteParser(),
|
create: (_) => PostBloc(repository),
|
||||||
routerDelegate: _appRouter.delegate(),
|
child: MaterialApp.router(
|
||||||
theme: ThemeData.light(),
|
routeInformationParser: _appRouter.defaultRouteParser(),
|
||||||
|
routerDelegate: _appRouter.delegate(),
|
||||||
|
theme: ThemeData.light(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:cherrypick/cherrypick.dart';
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
import 'package:talker_dio_logger/talker_dio_logger_interceptor.dart';
|
||||||
|
import 'package:talker_dio_logger/talker_dio_logger_settings.dart';
|
||||||
|
import 'package:talker_flutter/talker_flutter.dart';
|
||||||
import '../data/network/json_placeholder_api.dart';
|
import '../data/network/json_placeholder_api.dart';
|
||||||
import '../data/post_repository_impl.dart';
|
import '../data/post_repository_impl.dart';
|
||||||
import '../domain/repository/post_repository.dart';
|
import '../domain/repository/post_repository.dart';
|
||||||
@@ -9,6 +12,18 @@ part 'app_module.module.cherrypick.g.dart';
|
|||||||
|
|
||||||
@module()
|
@module()
|
||||||
abstract class AppModule extends Module {
|
abstract class AppModule extends Module {
|
||||||
|
@provide()
|
||||||
|
@singleton()
|
||||||
|
TalkerDioLoggerSettings talkerDioLoggerSettings() => TalkerDioLoggerSettings(
|
||||||
|
printRequestHeaders: true,
|
||||||
|
printResponseHeaders: true,
|
||||||
|
printResponseMessage: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
@provide()
|
||||||
|
@singleton()
|
||||||
|
TalkerDioLogger talkerDioLogger(Talker talker, TalkerDioLoggerSettings settings) => TalkerDioLogger(talker: talker, settings: settings);
|
||||||
|
|
||||||
@instance()
|
@instance()
|
||||||
int timeout() => 1000;
|
int timeout() => 1000;
|
||||||
|
|
||||||
@@ -35,8 +50,8 @@ abstract class AppModule extends Module {
|
|||||||
@provide()
|
@provide()
|
||||||
@singleton()
|
@singleton()
|
||||||
@named('dio')
|
@named('dio')
|
||||||
Dio dio(@named('baseUrl') String baseUrl) =>
|
Dio dio(@named('baseUrl') String baseUrl, TalkerDioLogger logger) =>
|
||||||
Dio(BaseOptions(baseUrl: baseUrl));
|
Dio(BaseOptions(baseUrl: baseUrl))..interceptors.add(logger);
|
||||||
|
|
||||||
@provide()
|
@provide()
|
||||||
@singleton()
|
@singleton()
|
||||||
|
|||||||
13
examples/postly/lib/di/core_module.dart
Normal file
13
examples/postly/lib/di/core_module.dart
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
import 'package:talker_flutter/talker_flutter.dart';
|
||||||
|
|
||||||
|
class CoreModule extends Module {
|
||||||
|
final Talker _talker;
|
||||||
|
|
||||||
|
CoreModule({required Talker talker}) : _talker = talker;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void builder(Scope currentScope) {
|
||||||
|
bind<Talker>().toProvide(() => _talker).singleton();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +1,30 @@
|
|||||||
import 'package:cherrypick/cherrypick.dart';
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:postly/app.dart';
|
import 'package:postly/app.dart';
|
||||||
|
import 'package:postly/di/core_module.dart';
|
||||||
|
import 'package:talker_bloc_logger/talker_bloc_logger_observer.dart';
|
||||||
|
import 'package:talker_flutter/talker_flutter.dart';
|
||||||
import 'di/app_module.dart';
|
import 'di/app_module.dart';
|
||||||
|
import 'package:talker_cherrypick_logger/talker_cherrypick_logger.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
final talker = Talker();
|
||||||
|
final talkerLogger = TalkerCherryPickObserver(talker);
|
||||||
|
|
||||||
|
|
||||||
|
Bloc.observer = TalkerBlocObserver(talker: talker);
|
||||||
|
|
||||||
|
CherryPick.setGlobalObserver(talkerLogger);
|
||||||
// Включаем cycle-detection только в debug/test
|
// Включаем cycle-detection только в debug/test
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
CherryPick.setGlobalLogger(PrintLogger());
|
|
||||||
CherryPick.enableGlobalCycleDetection();
|
CherryPick.enableGlobalCycleDetection();
|
||||||
CherryPick.enableGlobalCrossScopeCycleDetection();
|
CherryPick.enableGlobalCrossScopeCycleDetection();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Используем safe root scope для гарантии защиты
|
// Используем safe root scope для гарантии защиты
|
||||||
CherryPick.openRootScope().installModules([$AppModule()]);
|
CherryPick.openRootScope().installModules([CoreModule(talker: talker), $AppModule()]);
|
||||||
runApp(MyApp());
|
|
||||||
|
runApp(MyApp(talker: talker,));
|
||||||
}
|
}
|
||||||
|
|||||||
15
examples/postly/lib/presentation/pages/logs_page.dart
Normal file
15
examples/postly/lib/presentation/pages/logs_page.dart
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:talker_flutter/talker_flutter.dart';
|
||||||
|
import '../../app.dart';
|
||||||
|
|
||||||
|
@RoutePage()
|
||||||
|
class LogsPage extends StatelessWidget {
|
||||||
|
const LogsPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final talker = TalkerProvider.of(context);
|
||||||
|
return TalkerScreen(talker: talker);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,18 @@ class PostsPage extends StatelessWidget {
|
|||||||
create: (context) =>
|
create: (context) =>
|
||||||
context.read<PostBloc>()..add(const PostEvent.fetchAll()),
|
context.read<PostBloc>()..add(const PostEvent.fetchAll()),
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
appBar: AppBar(title: const Text('Posts')),
|
appBar: AppBar(
|
||||||
|
title: const Text('Posts'),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.bug_report),
|
||||||
|
tooltip: 'Open logs',
|
||||||
|
onPressed: () {
|
||||||
|
AutoRouter.of(context).push(const LogsRoute());
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
body: BlocBuilder<PostBloc, PostState>(
|
body: BlocBuilder<PostBloc, PostState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return state.when(
|
return state.when(
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
|
||||||
import 'app_router.gr.dart';
|
import 'app_router.gr.dart';
|
||||||
|
|
||||||
@AutoRouterConfig()
|
@AutoRouterConfig()
|
||||||
@@ -8,5 +7,6 @@ class AppRouter extends RootStackRouter {
|
|||||||
List<AutoRoute> get routes => [
|
List<AutoRoute> get routes => [
|
||||||
AutoRoute(page: PostsRoute.page, initial: true),
|
AutoRoute(page: PostsRoute.page, initial: true),
|
||||||
AutoRoute(page: PostDetailsRoute.page),
|
AutoRoute(page: PostDetailsRoute.page),
|
||||||
|
AutoRoute(page: LogsRoute.page),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.4.5"
|
version: "7.4.5"
|
||||||
|
ansi_styles:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: ansi_styles
|
||||||
|
sha256: "9c656cc12b3c27b17dd982b2cc5c0cfdfbdabd7bc8f3ae5e8542d9867b47ce8a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.2+1"
|
||||||
|
ansicolor:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: ansicolor
|
||||||
|
sha256: "50e982d500bc863e1d703448afdbf9e5a72eb48840a4f766fa361ffd6877055f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.3"
|
||||||
args:
|
args:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -42,7 +58,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "9.3.0+1"
|
version: "9.3.0+1"
|
||||||
auto_route_generator:
|
auto_route_generator:
|
||||||
dependency: "direct dev"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: auto_route_generator
|
name: auto_route_generator
|
||||||
sha256: c2e359d8932986d4d1bcad7a428143f81384ce10fef8d4aa5bc29e1f83766a46
|
sha256: c2e359d8932986d4d1bcad7a428143f81384ce10fef8d4aa5bc29e1f83766a46
|
||||||
@@ -98,7 +114,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.4"
|
version: "2.4.4"
|
||||||
build_runner:
|
build_runner:
|
||||||
dependency: "direct dev"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: build_runner
|
name: build_runner
|
||||||
sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99"
|
sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99"
|
||||||
@@ -137,6 +153,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.0"
|
||||||
|
charcode:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: charcode
|
||||||
|
sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
checked_yaml:
|
checked_yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -151,21 +175,37 @@ packages:
|
|||||||
path: "../../cherrypick"
|
path: "../../cherrypick"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "3.0.0-dev.3"
|
version: "3.0.0-dev.7"
|
||||||
cherrypick_annotations:
|
cherrypick_annotations:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "../../cherrypick_annotations"
|
path: "../../cherrypick_annotations"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "1.1.0"
|
version: "1.1.1"
|
||||||
cherrypick_generator:
|
cherrypick_generator:
|
||||||
dependency: "direct dev"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "../../cherrypick_generator"
|
path: "../../cherrypick_generator"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "1.1.0"
|
version: "1.1.1"
|
||||||
|
cli_launcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cli_launcher
|
||||||
|
sha256: "67d89e0a1c07b103d1253f6b953a43d3f502ee36805c8cfc21196282c9ddf177"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.2"
|
||||||
|
cli_util:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cli_util
|
||||||
|
sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.4.2"
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -190,6 +230,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.19.1"
|
version: "1.19.1"
|
||||||
|
conventional_commit:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: conventional_commit
|
||||||
|
sha256: fad254feb6fb8eace2be18855176b0a4b97e0d50e416ff0fe590d5ba83735d34
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.6.1"
|
||||||
convert:
|
convert:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -198,6 +246,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.2"
|
version: "3.1.2"
|
||||||
|
cross_file:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cross_file
|
||||||
|
sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.4+2"
|
||||||
crypto:
|
crypto:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -254,6 +310,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.2"
|
version: "1.3.2"
|
||||||
|
ffi:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: ffi
|
||||||
|
sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.4"
|
||||||
file:
|
file:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -284,7 +348,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "9.1.1"
|
version: "9.1.1"
|
||||||
flutter_lints:
|
flutter_lints:
|
||||||
dependency: "direct dev"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_lints
|
name: flutter_lints
|
||||||
sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
|
sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
|
||||||
@@ -292,12 +356,17 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.0"
|
version: "5.0.0"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct main"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
flutter_web_plugins:
|
||||||
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
freezed:
|
freezed:
|
||||||
dependency: "direct dev"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: freezed
|
name: freezed
|
||||||
sha256: "59a584c24b3acdc5250bb856d0d3e9c0b798ed14a4af1ddb7dc1c7b41df91c9c"
|
sha256: "59a584c24b3acdc5250bb856d0d3e9c0b798ed14a4af1ddb7dc1c7b41df91c9c"
|
||||||
@@ -336,6 +405,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.2"
|
version: "2.3.2"
|
||||||
|
group_button:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: group_button
|
||||||
|
sha256: "0610fcf28ed122bfb4b410fce161a390f7f2531d55d1d65c5375982001415940"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.3.4"
|
||||||
http:
|
http:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -360,6 +437,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.2"
|
version: "4.0.2"
|
||||||
|
intl:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: intl
|
||||||
|
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.19.0"
|
||||||
io:
|
io:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -385,7 +470,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "4.9.0"
|
version: "4.9.0"
|
||||||
json_serializable:
|
json_serializable:
|
||||||
dependency: "direct dev"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: json_serializable
|
name: json_serializable
|
||||||
sha256: c50ef5fc083d5b5e12eef489503ba3bf5ccc899e487d691584699b4bdefeea8c
|
sha256: c50ef5fc083d5b5e12eef489503ba3bf5ccc899e487d691584699b4bdefeea8c
|
||||||
@@ -448,6 +533,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.11.1"
|
version: "0.11.1"
|
||||||
|
melos:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: melos
|
||||||
|
sha256: "3f3ab3f902843d1e5a1b1a4dd39a4aca8ba1056f2d32fd8995210fa2843f646f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.3.2"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -464,6 +557,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.0"
|
version: "2.0.0"
|
||||||
|
mustache_template:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: mustache_template
|
||||||
|
sha256: a46e26f91445bfb0b60519be280555b06792460b27b19e2b19ad5b9740df5d1c
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
nested:
|
nested:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -488,6 +589,54 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.1"
|
version: "1.9.1"
|
||||||
|
path_provider:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider
|
||||||
|
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.5"
|
||||||
|
path_provider_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_android
|
||||||
|
sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.17"
|
||||||
|
path_provider_foundation:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_foundation
|
||||||
|
sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.1"
|
||||||
|
path_provider_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_linux
|
||||||
|
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.1"
|
||||||
|
path_provider_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_platform_interface
|
||||||
|
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
path_provider_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_windows
|
||||||
|
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.0"
|
||||||
petitparser:
|
petitparser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -496,6 +645,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.2"
|
version: "6.0.2"
|
||||||
|
platform:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: platform
|
||||||
|
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.6"
|
||||||
|
plugin_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: plugin_platform_interface
|
||||||
|
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.8"
|
||||||
pool:
|
pool:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -504,6 +669,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.5.1"
|
version: "1.5.1"
|
||||||
|
process:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: process
|
||||||
|
sha256: c6248e4526673988586e8c00bb22a49210c258dc91df5227d5da9748ecf79744
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.0.5"
|
||||||
|
prompts:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: prompts
|
||||||
|
sha256: "3773b845e85a849f01e793c4fc18a45d52d7783b4cb6c0569fad19f9d0a774a1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
protobuf:
|
protobuf:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -528,6 +709,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.2.0"
|
||||||
|
pub_updater:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pub_updater
|
||||||
|
sha256: "54e8dc865349059ebe7f163d6acce7c89eb958b8047e6d6e80ce93b13d7c9e60"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.4.0"
|
||||||
pubspec_parse:
|
pubspec_parse:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -545,13 +734,29 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "4.4.2"
|
version: "4.4.2"
|
||||||
retrofit_generator:
|
retrofit_generator:
|
||||||
dependency: "direct dev"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: retrofit_generator
|
name: retrofit_generator
|
||||||
sha256: "65d28d3a7b4db485f1c73fee8ee32f552ef23ee4ecb68ba491f39d80b73bdcbf"
|
sha256: "65d28d3a7b4db485f1c73fee8ee32f552ef23ee4ecb68ba491f39d80b73bdcbf"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.2.0"
|
version: "9.2.0"
|
||||||
|
share_plus:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: share_plus
|
||||||
|
sha256: b2961506569e28948d75ec346c28775bb111986bb69dc6a20754a457e3d97fa0
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "11.0.0"
|
||||||
|
share_plus_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: share_plus_platform_interface
|
||||||
|
sha256: "1032d392bc5d2095a77447a805aa3f804d2ae6a4d5eef5e6ebb3bd94c1bc19ef"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.0"
|
||||||
shelf:
|
shelf:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -597,6 +802,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.10.1"
|
version: "1.10.1"
|
||||||
|
sprintf:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sprintf
|
||||||
|
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.0"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -629,6 +842,53 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.1"
|
version: "1.4.1"
|
||||||
|
talker:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: talker
|
||||||
|
sha256: "028a753874d98df39f210cb74f0ee09a0a95e28f8bc2dc975c3c328e24fde23d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.9.3"
|
||||||
|
talker_bloc_logger:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: talker_bloc_logger
|
||||||
|
sha256: cf1e3b1d70f9a47e061288f0d230ba0e04a0f6394629d5df1c7b0933b236e397
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.9.3"
|
||||||
|
talker_cherrypick_logger:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: "../../talker_cherrypick_logger"
|
||||||
|
relative: true
|
||||||
|
source: path
|
||||||
|
version: "1.0.0"
|
||||||
|
talker_dio_logger:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: talker_dio_logger
|
||||||
|
sha256: dcf784f1841e248c270ef741f8a07ca9cf562c6424ee43fc6e598c4eb7f18238
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.9.3"
|
||||||
|
talker_flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: talker_flutter
|
||||||
|
sha256: "2cfee6661277d415a895b6258ecb0bf80d7b564e91ea7e769fc6d0f970a01c09"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.9.3"
|
||||||
|
talker_logger:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: talker_logger
|
||||||
|
sha256: "778ec673f1b71a6516e5576ae8d90ea23bbbcf9f405a97cc30e8ccdc33e26d27"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.9.3"
|
||||||
term_glyph:
|
term_glyph:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -661,6 +921,46 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.0"
|
||||||
|
url_launcher_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_linux
|
||||||
|
sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.1"
|
||||||
|
url_launcher_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_platform_interface
|
||||||
|
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.2"
|
||||||
|
url_launcher_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_web
|
||||||
|
sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.1"
|
||||||
|
url_launcher_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_windows
|
||||||
|
sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.4"
|
||||||
|
uuid:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: uuid
|
||||||
|
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.5.1"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -709,6 +1009,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.3"
|
version: "3.0.3"
|
||||||
|
win32:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: win32
|
||||||
|
sha256: "329edf97fdd893e0f1e3b9e88d6a0e627128cc17cc316a8d67fda8f1451178ba"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.13.0"
|
||||||
|
xdg_directories:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: xdg_directories
|
||||||
|
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
xml:
|
xml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -725,6 +1041,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.3"
|
version: "3.1.3"
|
||||||
|
yaml_edit:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: yaml_edit
|
||||||
|
sha256: fb38626579fb345ad00e674e2af3a5c9b0cc4b9bfb8fd7f7ff322c7c9e62aef5
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.2"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.7.0 <4.0.0"
|
dart: ">=3.7.0 <4.0.0"
|
||||||
flutter: ">=3.18.0-18.0.pre.54"
|
flutter: ">=3.27.0"
|
||||||
|
|||||||
@@ -24,9 +24,12 @@ dependencies:
|
|||||||
flutter_bloc: ^9.1.1
|
flutter_bloc: ^9.1.1
|
||||||
auto_route: ^9.3.0+1
|
auto_route: ^9.3.0+1
|
||||||
|
|
||||||
|
|
||||||
cupertino_icons: ^1.0.8
|
cupertino_icons: ^1.0.8
|
||||||
|
|
||||||
dev_dependencies:
|
talker_flutter: ^4.9.3
|
||||||
|
talker_cherrypick_logger:
|
||||||
|
path: ../../talker_cherrypick_logger
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
@@ -40,7 +43,11 @@ dev_dependencies:
|
|||||||
freezed: ^2.5.8
|
freezed: ^2.5.8
|
||||||
json_serializable: ^6.9.0
|
json_serializable: ^6.9.0
|
||||||
auto_route_generator: ^9.0.0
|
auto_route_generator: ^9.0.0
|
||||||
|
talker_dio_logger: ^4.9.3
|
||||||
|
talker_bloc_logger: ^4.9.3
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
|
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
dev_dependencies:
|
||||||
|
melos: ^6.3.2
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ packages:
|
|||||||
- cherrypick_flutter
|
- cherrypick_flutter
|
||||||
- cherrypick_annotations
|
- cherrypick_annotations
|
||||||
- cherrypick_generator
|
- cherrypick_generator
|
||||||
|
- talker_cherrypick_logger
|
||||||
- examples/client_app
|
- examples/client_app
|
||||||
- examples/postly
|
- examples/postly
|
||||||
|
|
||||||
|
|||||||
7
talker_cherrypick_logger/.gitignore
vendored
Normal file
7
talker_cherrypick_logger/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# https://dart.dev/guides/libraries/private-files
|
||||||
|
# Created by `dart pub`
|
||||||
|
.dart_tool/
|
||||||
|
|
||||||
|
# Avoid committing pubspec.lock for library packages; see
|
||||||
|
# https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||||
|
pubspec.lock
|
||||||
3
talker_cherrypick_logger/CHANGELOG.md
Normal file
3
talker_cherrypick_logger/CHANGELOG.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
## 1.0.0
|
||||||
|
|
||||||
|
- Initial version.
|
||||||
201
talker_cherrypick_logger/LICENSE
Normal file
201
talker_cherrypick_logger/LICENSE
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
122
talker_cherrypick_logger/README.md
Normal file
122
talker_cherrypick_logger/README.md
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
# talker_cherrypick_logger
|
||||||
|
|
||||||
|
An integration package that allows you to log [CherryPick](https://github.com/pese-dot-work/cherrypick) Dependency Injection (DI) container events using the [Talker](https://pub.dev/packages/talker) logging system.
|
||||||
|
All CherryPick lifecycle events, instance creations, cache operations, module activities, cycles, and errors are routed directly to your Talker logger for easy debugging and advanced diagnostics.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Automatic DI container logging:**
|
||||||
|
All core CherryPick events (instance creation/disposal, cache hits/misses, module install/removal, scopes, cycles, errors) are logged through Talker.
|
||||||
|
- **Flexible log levels:**
|
||||||
|
Each event uses the appropriate Talker log level (`info`, `warning`, `verbose`, `handle` for errors).
|
||||||
|
- **Works with any Talker setup:**
|
||||||
|
No extra dependencies required except Talker and CherryPick.
|
||||||
|
- **Improves debugging and DI transparency** in both development and production.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
|
||||||
|
### 1. Add dependencies
|
||||||
|
|
||||||
|
In your `pubspec.yaml`:
|
||||||
|
```yaml
|
||||||
|
dependencies:
|
||||||
|
cherrypick: ^latest
|
||||||
|
talker: ^latest
|
||||||
|
talker_cherrypick_logger:
|
||||||
|
git:
|
||||||
|
url: https://github.com/pese-dot-work/cherrypick.git
|
||||||
|
path: talker_cherrypick_logger
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Import the package
|
||||||
|
```dart
|
||||||
|
import 'package:talker/talker.dart';
|
||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
import 'package:talker_cherrypick_logger/talker_cherrypick_logger.dart';
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Basic integration
|
||||||
|
|
||||||
|
1. **Create a Talker instance** (optionally customize Talker as you wish):
|
||||||
|
```dart
|
||||||
|
final talker = Talker();
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Create the observer and pass it to CherryPick:**
|
||||||
|
```dart
|
||||||
|
final observer = TalkerCherryPickObserver(talker);
|
||||||
|
|
||||||
|
// On DI setup, pass observer when opening (or re-opening) root or any custom scope
|
||||||
|
CherryPick.openRootScope(observer: observer);
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Now all DI events appear in your Talker logs!**
|
||||||
|
|
||||||
|
#### Example log output
|
||||||
|
|
||||||
|
- `[binding][CherryPick] MyService — MyServiceImpl (scope: root)`
|
||||||
|
- `[create][CherryPick] MyService — MyServiceImpl => Instance(...) (scope: root)`
|
||||||
|
- `[cache hit][CherryPick] MyService — MyServiceImpl (scope: root)`
|
||||||
|
- `[cycle][CherryPick] Detected: A -> B -> C -> A (scope: root)`
|
||||||
|
- `[error][CherryPick] Failed to resolve dependency`
|
||||||
|
- `[diagnostic][CherryPick] Cache cleared`
|
||||||
|
|
||||||
|
#### How it works
|
||||||
|
|
||||||
|
`TalkerCherryPickObserver` implements `CherryPickObserver` and routes all methods/events to Talker:
|
||||||
|
- Regular events: `.info()`
|
||||||
|
- DI Warnings and cycles: `.warning()`
|
||||||
|
- Diagnostics: `.verbose()`
|
||||||
|
- Errors: `.handle()` (so they are visible in Talker error console, with stack trace)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Extended example
|
||||||
|
|
||||||
|
```dart
|
||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
import 'package:talker/talker.dart';
|
||||||
|
import 'package:talker_cherrypick_logger/talker_cherrypick_logger.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
final talker = Talker();
|
||||||
|
final observer = TalkerCherryPickObserver(talker);
|
||||||
|
|
||||||
|
// Optionally: customize Talker output or filtering
|
||||||
|
// talker.settings.logLevel = TalkerLogLevel.debug;
|
||||||
|
|
||||||
|
CherryPick.openRootScope(observer: observer);
|
||||||
|
|
||||||
|
// ...setup your DI modules as usual
|
||||||
|
// All container events will appear in Talker logs for easy debugging!
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Additional information
|
||||||
|
|
||||||
|
- This package is especially useful for debugging large or layered projects using CherryPick.
|
||||||
|
- For advanced Talker configurations (UI, outputs to remote, filtering), see the [Talker documentation](https://pub.dev/packages/talker).
|
||||||
|
- This package does **not** interfere with DI graph construction or your app's behavior — it's purely diagnostic.
|
||||||
|
- For questions or issues, open an issue on the main [cherrypick repository](https://github.com/pese-dot-work/cherrypick).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Feel free to contribute improvements or report bugs via pull requests or issues!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
See [LICENSE](LICENSE) for details.
|
||||||
30
talker_cherrypick_logger/analysis_options.yaml
Normal file
30
talker_cherrypick_logger/analysis_options.yaml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# This file configures the static analysis results for your project (errors,
|
||||||
|
# warnings, and lints).
|
||||||
|
#
|
||||||
|
# This enables the 'recommended' set of lints from `package:lints`.
|
||||||
|
# This set helps identify many issues that may lead to problems when running
|
||||||
|
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
|
||||||
|
# style and format.
|
||||||
|
#
|
||||||
|
# If you want a smaller set of lints you can change this to specify
|
||||||
|
# 'package:lints/core.yaml'. These are just the most critical lints
|
||||||
|
# (the recommended set includes the core lints).
|
||||||
|
# The core lints are also what is used by pub.dev for scoring packages.
|
||||||
|
|
||||||
|
include: package:lints/recommended.yaml
|
||||||
|
|
||||||
|
# Uncomment the following section to specify additional rules.
|
||||||
|
|
||||||
|
# linter:
|
||||||
|
# rules:
|
||||||
|
# - camel_case_types
|
||||||
|
|
||||||
|
# analyzer:
|
||||||
|
# exclude:
|
||||||
|
# - path/to/excluded/files/**
|
||||||
|
|
||||||
|
# For more information about the core and recommended set of lints, see
|
||||||
|
# https://dart.dev/go/core-lints
|
||||||
|
|
||||||
|
# For additional information about configuring this file, see
|
||||||
|
# https://dart.dev/guides/language/analysis-options
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import 'package:talker_cherrypick_logger/talker_cherrypick_logger.dart';
|
||||||
|
import 'package:talker/talker.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
final talker = Talker();
|
||||||
|
final logger = TalkerCherryPickObserver(talker);
|
||||||
|
|
||||||
|
logger.onDiagnostic('Hello from CherryPickLogger!');
|
||||||
|
logger.onWarning('Something might be wrong...');
|
||||||
|
logger.onError('Oops! An error occurred', Exception('Test error'), null);
|
||||||
|
|
||||||
|
// Вывод всех логов
|
||||||
|
print('\nВсе сообщения логирования через Talker:');
|
||||||
|
for (final log in talker.history) {
|
||||||
|
print(log); // Пример, либо log.toString(), либо log.message
|
||||||
|
}
|
||||||
|
}
|
||||||
141
talker_cherrypick_logger/lib/src/talker_cherrypick_observer.dart
Normal file
141
talker_cherrypick_logger/lib/src/talker_cherrypick_observer.dart
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2021 Sergey Penkovsky (sergey.penkovsky@gmail.com)
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
import 'package:talker/talker.dart';
|
||||||
|
|
||||||
|
/// An implementation of [CherryPickObserver] that logs all DI container events
|
||||||
|
/// through the [Talker] logging system.
|
||||||
|
///
|
||||||
|
/// This observer allows you to automatically route all important events from the
|
||||||
|
/// CherryPick DI container (such as instance creation, cache hits, errors, module install,
|
||||||
|
/// scope lifecycle events, and more) directly to your Talker logger. It is useful for
|
||||||
|
/// debugging, monitoring, and analytics.
|
||||||
|
///
|
||||||
|
/// ## Example usage
|
||||||
|
/// ```dart
|
||||||
|
/// import 'package:talker/talker.dart';
|
||||||
|
/// import 'package:cherrypick/cherrypick.dart';
|
||||||
|
/// import 'package:talker_cherrypick_logger/talker_cherrypick_logger.dart';
|
||||||
|
///
|
||||||
|
/// final talker = Talker();
|
||||||
|
/// final observer = TalkerCherryPickObserver(talker);
|
||||||
|
///
|
||||||
|
/// // Pass the observer to your CherryPick root scope (or any scope)
|
||||||
|
/// CherryPick.openRootScope(observer: observer);
|
||||||
|
///
|
||||||
|
/// // Now all DI container events will be logged with Talker
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Logged event examples
|
||||||
|
/// - "[binding][CherryPick] MyService — MyServiceImpl (scope: root)"
|
||||||
|
/// - "[create][CherryPick] MyService — MyServiceImpl => Instance(...) (scope: root)"
|
||||||
|
/// - "[cache hit][CherryPick] MyService — MyServiceImpl (scope: root)"
|
||||||
|
/// - "[cycle][CherryPick] Detected: A -> B -> C -> A (scope: root)"
|
||||||
|
///
|
||||||
|
/// ## Log levels mapping
|
||||||
|
/// - `info`: regular events (registered, resolved, created, disposed, modules, scopes, cache hits/misses)
|
||||||
|
/// - `warning`: cycles, cherry pick warnings
|
||||||
|
/// - `verbose`: diagnostics
|
||||||
|
/// - `handle`: errors (includes error object/stack)
|
||||||
|
class TalkerCherryPickObserver implements CherryPickObserver {
|
||||||
|
/// The target [Talker] instance to send logs to.
|
||||||
|
final Talker talker;
|
||||||
|
|
||||||
|
/// Creates a [TalkerCherryPickObserver] that routes CherryPick DI events into the given [Talker] logger.
|
||||||
|
TalkerCherryPickObserver(this.talker);
|
||||||
|
|
||||||
|
/// Called when a binding (dependency mapping) is registered in the DI container.
|
||||||
|
@override
|
||||||
|
void onBindingRegistered(String name, Type type, {String? scopeName}) {
|
||||||
|
talker.info('[binding][CherryPick] $name — $type (scope: $scopeName)');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when an instance is requested (before creation or retrieval).
|
||||||
|
@override
|
||||||
|
void onInstanceRequested(String name, Type type, {String? scopeName}) {
|
||||||
|
talker.info('[request][CherryPick] $name — $type (scope: $scopeName)');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when a new instance is created.
|
||||||
|
@override
|
||||||
|
void onInstanceCreated(String name, Type type, Object instance, {String? scopeName}) {
|
||||||
|
talker.info('[create][CherryPick] $name — $type => $instance (scope: $scopeName)');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when an instance is disposed.
|
||||||
|
@override
|
||||||
|
void onInstanceDisposed(String name, Type type, Object instance, {String? scopeName}) {
|
||||||
|
talker.info('[dispose][CherryPick] $name — $type => $instance (scope: $scopeName)');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when modules are installed.
|
||||||
|
@override
|
||||||
|
void onModulesInstalled(List<String> modules, {String? scopeName}) {
|
||||||
|
talker.info('[modules installed][CherryPick] ${modules.join(', ')} (scope: $scopeName)');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when modules are removed.
|
||||||
|
@override
|
||||||
|
void onModulesRemoved(List<String> modules, {String? scopeName}) {
|
||||||
|
talker.info('[modules removed][CherryPick] ${modules.join(', ')} (scope: $scopeName)');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when a DI scope is opened.
|
||||||
|
@override
|
||||||
|
void onScopeOpened(String name) {
|
||||||
|
talker.info('[scope opened][CherryPick] $name');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when a DI scope is closed.
|
||||||
|
@override
|
||||||
|
void onScopeClosed(String name) {
|
||||||
|
talker.info('[scope closed][CherryPick] $name');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called if the DI container detects a cycle in the dependency graph.
|
||||||
|
@override
|
||||||
|
void onCycleDetected(List<String> chain, {String? scopeName}) {
|
||||||
|
talker.warning('[cycle][CherryPick] Detected: ${chain.join(' -> ')}${scopeName != null ? ' (scope: $scopeName)' : ''}');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when an instance is found in the cache.
|
||||||
|
@override
|
||||||
|
void onCacheHit(String name, Type type, {String? scopeName}) {
|
||||||
|
talker.info('[cache hit][CherryPick] $name — $type (scope: $scopeName)');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when an instance is NOT found in the cache and will be created.
|
||||||
|
@override
|
||||||
|
void onCacheMiss(String name, Type type, {String? scopeName}) {
|
||||||
|
talker.info('[cache miss][CherryPick] $name — $type (scope: $scopeName)');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called for generic diagnostic/debug events.
|
||||||
|
@override
|
||||||
|
void onDiagnostic(String message, {Object? details}) {
|
||||||
|
talker.verbose('[diagnostic][CherryPick] $message ${details ?? ''}');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called for non-fatal DI container warnings.
|
||||||
|
@override
|
||||||
|
void onWarning(String message, {Object? details}) {
|
||||||
|
talker.warning('[warn][CherryPick] $message ${details ?? ''}');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called for error events with optional stack trace.
|
||||||
|
@override
|
||||||
|
void onError(String message, Object? error, StackTrace? stackTrace) {
|
||||||
|
talker.handle(error ?? '[CherryPick] $message', stackTrace, '[error][CherryPick] $message');
|
||||||
|
}
|
||||||
|
}
|
||||||
18
talker_cherrypick_logger/lib/talker_cherrypick_logger.dart
Normal file
18
talker_cherrypick_logger/lib/talker_cherrypick_logger.dart
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2021 Sergey Penkovsky (sergey.penkovsky@gmail.com)
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
library;
|
||||||
|
|
||||||
|
export 'src/talker_cherrypick_observer.dart';
|
||||||
|
|
||||||
|
// TODO: Export any libraries intended for clients of this package.
|
||||||
18
talker_cherrypick_logger/pubspec.yaml
Normal file
18
talker_cherrypick_logger/pubspec.yaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
name: talker_cherrypick_logger
|
||||||
|
description: A starting point for Dart libraries or applications.
|
||||||
|
version: 1.0.0
|
||||||
|
publish_to: none
|
||||||
|
# repository: https://github.com/my_org/my_repo
|
||||||
|
|
||||||
|
environment:
|
||||||
|
sdk: ^3.7.2
|
||||||
|
|
||||||
|
# Add regular dependencies here.
|
||||||
|
dependencies:
|
||||||
|
talker: ^4.9.3
|
||||||
|
cherrypick: ^3.0.0-dev.8
|
||||||
|
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
lints: ^5.0.0
|
||||||
|
test: ^1.24.0
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'package:talker/talker.dart';
|
||||||
|
import 'package:talker_cherrypick_logger/talker_cherrypick_logger.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('TalkerCherryPickObserver', () {
|
||||||
|
late Talker talker;
|
||||||
|
late TalkerCherryPickObserver observer;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
talker = Talker();
|
||||||
|
observer = TalkerCherryPickObserver(talker);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('onInstanceRequested logs info', () {
|
||||||
|
observer.onInstanceRequested('A', String, scopeName: 'test');
|
||||||
|
final log = talker.history.last;
|
||||||
|
expect(log.message, contains('[request][CherryPick] A — String (scope: test)'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('onCycleDetected logs warning', () {
|
||||||
|
observer.onCycleDetected(['A', 'B'], scopeName: 's');
|
||||||
|
final log = talker.history.last;
|
||||||
|
expect(log.message, contains('[cycle][CherryPick] Detected'));
|
||||||
|
//expect(log.level, TalkerLogLevel.warning);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('onError calls handle', () {
|
||||||
|
final error = Exception('fail');
|
||||||
|
final stack = StackTrace.current;
|
||||||
|
observer.onError('Oops', error, stack);
|
||||||
|
final log = talker.history.last;
|
||||||
|
expect(log.message, contains('[error][CherryPick] Oops'));
|
||||||
|
expect(log.exception, error);
|
||||||
|
expect(log.stackTrace, stack);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('onDiagnostic logs verbose', () {
|
||||||
|
observer.onDiagnostic('hello', details: 123);
|
||||||
|
final log = talker.history.last;
|
||||||
|
//expect(log.level, TalkerLogLevel.verbose);
|
||||||
|
expect(log.message, contains('hello'));
|
||||||
|
expect(log.message, contains('123'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user