update readme

This commit is contained in:
Sergey Penkovsky
2025-05-19 16:06:51 +03:00
parent 2607a69bca
commit de995228a5

View File

@@ -1,89 +1,115 @@
# CherryPick Flutter
# CherryPick
`cherrypick_flutter` is a robust Flutter library designed for managing and accessing dependencies using a scope context provided by `CherryPickProvider`. It enhances your application's modularity and testability by simplifying dependency injection.
`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.
## Quick Start
## Key Concepts
### Core Components of Dependency Injection (DI)
### Binding
#### Binding
A **Binding** acts as a configuration for how to create or provide a particular dependency. Bindings support:
A Binding is a custom instance configurator crucial for setting up dependencies. It offers the following key methods:
- `toInstance()`: Directly provides an initialized instance.
- `toProvide()`: Accepts a provider function for lazy initialization.
- `toProvideAsync()`: Accepts an asynchronous provider for lazy initialization.
- `toProvideWithParams()`: Accepts a provider function requiring dynamic parameters.
- `toProvideAsyncWithParams()`: Accepts an asynchronous provider requiring dynamic parameters.
- `withName()`: Assigns a name for instance retrieval by name.
- `singleton()`: Marks the instance as a singleton, ensuring only one instance exists within the scope.
- Direct instance assignment (`toInstance()`, `toInstanceAsync()`)
- Lazy providers (sync/async functions)
- Provider functions supporting dynamic parameters
- Named instances for resolving by string key
- Optional singleton lifecycle
##### Example:
#### Example
```dart
// Direct instance initialization using toInstance()
Binding<String>().toInstance("hello world");
// Provide a direct instance
Binding<String>().toInstance("Hello world");
// Lazy initialization via provider
Binding<String>().toProvide(() => "hello world");
// Provide an async direct instance
Binding<String>().toInstanceAsync(Future.value("Hello world"));
// Asynchronous lazy initialization
Binding<String>().toProvideAsync(() async => "hello async world");
// Provide a lazy sync instance using a factory
Binding<String>().toProvide(() => "Hello world");
// Asynchronous lazy initialization with dynamic parameters
Binding<String>().toProvideAsyncWithParams((params) async => "hello $params");
// Provide a lazy async instance using a factory
Binding<String>().toProvideAsync(() async => "Hello async world");
// Initialization with dynamic parameters
Binding<String>().toProvideWithParams((params) => "hello $params");
// Provide an instance with dynamic parameters (sync)
Binding<String>().toProvideWithParams((params) => "Hello $params");
// Named instance for resolution
Binding<String>().toProvide(() => "hello world").withName("my_string").toInstance("hello world");
// Provide an instance with dynamic parameters (async)
Binding<String>().toProvideAsyncWithParams((params) async => "Hello $params");
// Singleton instance
Binding<String>().toProvide(() => "hello world").singleton();
// Named instance for retrieval by name
Binding<String>().toProvide(() => "Hello world").withName("my_string");
// Mark as singleton (only one instance within the scope)
Binding<String>().toProvide(() => "Hello world").singleton();
```
#### Module
### Module
A Module encapsulates bindings, logically organizing dependencies. Implement the `void builder(Scope currentScope)` method to create a custom module.
A **Module** is a logical collection point for bindings, designed for grouping and initializing related dependencies. Implement the `builder` method to define how dependencies should be bound within the scope.
##### Example:
#### Example
```dart
class AppModule extends Module {
@override
void builder(Scope currentScope) {
bind<ApiClient>().toInstance(ApiClientMock());
bind<String>().toProvide(() => "Hello world!");
}
}
```
#### Scope
### Scope
A Scope manages your dependency tree, holding modules and instances. Use the scope to access dependencies with `resolve<T>()` or `resolveAsync<T>()` for asynchronous operations.
A **Scope** manages a tree of modules and dependency instances. Scopes can be nested into hierarchies (parent-child), supporting modular app composition and context-specific overrides.
##### Example:
You typically work with the root scope, but can also create named subscopes as needed.
#### Example
```dart
// Open the main scope
// Open the main/root scope
final rootScope = CherryPick.openRootScope();
// Install custom modules
// Install a custom module
rootScope.installModules([AppModule()]);
// Resolve an instance
// Resolve a dependency synchronously
final str = rootScope.resolve<String>();
// Asynchronously resolve an instance
final asyncStr = await rootScope.resolveAsync<String>();
// Resolve a dependency asynchronously
final result = await rootScope.resolveAsync<String>();
// Close the main scope
// Close the root scope once done
CherryPick.closeRootScope();
```
#### Working with Subscopes
```dart
// Open a named child scope (e.g., for a feature/module)
final subScope = rootScope.openSubScope('featureScope')
..installModules([FeatureModule()]);
// Resolve from subScope, with fallback to parents if missing
final dataBloc = await subScope.resolveAsync<DataBloc>();
```
### Dependency Lookup API
- `resolve<T>()` — Locates a dependency instance or throws if missing.
- `resolveAsync<T>()` — Async variant for dependencies requiring async binding.
- `tryResolve<T>()` — Returns `null` if not found (sync).
- `tryResolveAsync<T>()` — Returns `null` async if not found.
Supports:
- Synchronous and asynchronous dependencies
- Named dependencies
- Provider functions with and without runtime parameters
## Example Application
The following example demonstrates module setup, scope management, and dependency resolution (both synchronous and asynchronous).
Below is a complete example illustrating modules, subscopes, async providers, and dependency resolution.
```dart
import 'dart:async';
@@ -100,27 +126,28 @@ class AppModule extends Module {
class FeatureModule extends Module {
final bool isMock;
FeatureModule({required this.isMock});
@override
void builder(Scope currentScope) {
// Using toProvideAsync for async initialization
// Async provider for DataRepository with named dependency selection
bind<DataRepository>()
.withName("networkRepo")
.toProvideAsync(() async {
final client = await Future.delayed(
Duration(milliseconds: 100),
() => currentScope.resolve<ApiClient>(
named: isMock ? "apiClientMock" : "apiClientImpl"));
Duration(milliseconds: 100),
() => currentScope.resolve<ApiClient>(
named: isMock ? "apiClientMock" : "apiClientImpl",
),
);
return NetworkDataRepository(client);
})
.singleton();
// Asynchronous initialization of DataBloc
// Chained async provider for DataBloc
bind<DataBloc>().toProvideAsync(
() async {
final repo = await currentScope.resolveAsync<DataRepository>(named: "networkRepo");
final repo = await currentScope.resolveAsync<DataRepository>(
named: "networkRepo");
return DataBloc(repo);
},
);
@@ -128,27 +155,24 @@ class FeatureModule extends Module {
}
void main() async {
final scope = openRootScope().installModules([
AppModule(),
]);
final scope = CherryPick.openRootScope().installModules([AppModule()]);
final featureScope = scope.openSubScope("featureScope")
..installModules([FeatureModule(isMock: true)]);
final subScope = scope
.openSubScope("featureScope")
.installModules([FeatureModule(isMock: true)]);
// Asynchronous instance resolution
final dataBloc = await subScope.resolveAsync<DataBloc>();
dataBloc.data.listen((d) => print('Received data: $d'),
onError: (e) => print('Error: $e'), onDone: () => print('DONE'));
final dataBloc = await featureScope.resolveAsync<DataBloc>();
dataBloc.data.listen(
(d) => print('Received data: $d'),
onError: (e) => print('Error: $e'),
onDone: () => print('DONE'),
);
await dataBloc.fetchData();
}
class DataBloc {
final DataRepository _dataRepository;
Stream<String> get data => _dataController.stream;
StreamController<String> _dataController = StreamController.broadcast();
final StreamController<String> _dataController = StreamController.broadcast();
DataBloc(this._dataRepository);
@@ -172,16 +196,19 @@ abstract class DataRepository {
class NetworkDataRepository implements DataRepository {
final ApiClient _apiClient;
final _token = 'token';
NetworkDataRepository(this._apiClient);
@override
Future<String> getData() async => await _apiClient.sendRequest(
url: 'www.google.com', token: _token, requestBody: {'type': 'data'});
Future<String> getData() async =>
await _apiClient.sendRequest(
url: 'www.google.com',
token: _token,
requestBody: {'type': 'data'},
);
}
abstract class ApiClient {
Future sendRequest({@required String url, String token, Map requestBody});
Future sendRequest({@required String? url, String? token, Map? requestBody});
}
class ApiClientMock implements ApiClient {
@@ -203,18 +230,23 @@ class ApiClientImpl implements ApiClient {
## Features
- [x] Main Scope and Sub Scopes
- [x] Named Instance Initialization
- [x] Asynchronous Dependency Resolution
- [x] Dynamic Parameter Support for Providers
- [x] Main Scope and Named Subscopes
- [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)
## Contributing
We welcome contributions from the community. Please feel free to submit issues or pull requests with suggestions or improvements.
Contributions are welcome! Please open issues or submit pull requests on [GitHub](https://github.com/pese-git/cherrypick).
## License
This project is licensed under the Apache License 2.0. You may obtain a copy of the License at [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0).
Licensed under the [Apache License 2.0](http://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.