diff --git a/cherrypick/README.md b/cherrypick/README.md index 8f76c97..a4499cb 100644 --- a/cherrypick/README.md +++ b/cherrypick/README.md @@ -1,31 +1,43 @@ # CherryPick Flutter -`cherrypick_flutter` is a powerful Flutter library for managing and accessing dependencies within your application through a root scope context using `CherryPickProvider`. It offers simplified dependency injection, making your application more modular and test-friendly. +`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. ## Quick Start -### Main Components in Dependency Injection (DI) +### Core Components of Dependency Injection (DI) #### Binding -A Binding is a custom instance configurator, essential for setting up dependencies. It offers the following key methods: +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 or constructor for lazy initialization. -- `withName()`: Assigns a name to an instance, allowing for retrieval by name. -- `singleton()`: Marks the instance as a singleton, ensuring only one instance exists in the scope. +- `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. ##### Example: ```dart -// Direct instance initialization with toInstance() +// Direct instance initialization using toInstance() Binding().toInstance("hello world"); -// Or use a provider for lazy initialization +// Lazy initialization via provider Binding().toProvide(() => "hello world"); -// Named instance -Binding().withName("my_string").toInstance("hello world"); +// Asynchronous lazy initialization +Binding().toProvideAsync(() async => "hello async world"); + +/ Asynchronous lazy initialization with dynamic parameters +Binding().toProvideAsyncWithParams((params) async => "hello $params"); + +// Initialization with dynamic parameters +Binding().toProvideWithParams((params) => "hello $params"); + +// Named instance for resolution +Binding().toProvide(() => "hello world").withName("my_string").toInstance("hello world"); // Singleton instance Binding().toProvide(() => "hello world").singleton(); @@ -33,7 +45,7 @@ Binding().toProvide(() => "hello world").singleton(); #### Module -A Module encapsulates bindings, allowing you to organize dependencies logically. To create a custom module, implement the `void builder(Scope currentScope)` method. +A Module encapsulates bindings, logically organizing dependencies. Implement the `void builder(Scope currentScope)` method to create a custom module. ##### Example: @@ -48,13 +60,13 @@ class AppModule extends Module { #### Scope -A Scope is the container that manages your dependency tree, holding modules and instances. Use the scope to access dependencies with the `resolve()` method. +A Scope manages your dependency tree, holding modules and instances. Use the scope to access dependencies with `resolve()` or `resolveAsync()` for asynchronous operations. ##### Example: ```dart // Open the main scope -final rootScope = Cherrypick.openRootScope(); +final rootScope = CherryPick.openRootScope(); // Install custom modules rootScope.installModules([AppModule()]); @@ -62,13 +74,16 @@ rootScope.installModules([AppModule()]); // Resolve an instance final str = rootScope.resolve(); +// Asynchronously resolve an instance +final asyncStr = await rootScope.resolveAsync(); + // Close the main scope -Cherrypick.closeRootScope(); +CherryPick.closeRootScope(); ``` ## Example Application -The following example demonstrates module setup, scope management, and dependency resolution. +The following example demonstrates module setup, scope management, and dependency resolution (both synchronous and asynchronous). ```dart import 'dart:async'; @@ -84,26 +99,30 @@ class AppModule extends Module { } class FeatureModule extends Module { - bool isMock; + final bool isMock; FeatureModule({required this.isMock}); @override void builder(Scope currentScope) { + // Using toProvideAsync for async initialization bind() .withName("networkRepo") - .toProvide( - () => NetworkDataRepository( - currentScope.resolve( - named: isMock ? "apiClientMock" : "apiClientImpl", - ), - ), - ) + .toProvideAsync(() async { + final client = await Future.delayed( + Duration(milliseconds: 100), + () => currentScope.resolve( + named: isMock ? "apiClientMock" : "apiClientImpl")); + return NetworkDataRepository(client); + }) .singleton(); - bind().toProvide( - () => DataBloc( - currentScope.resolve(named: "networkRepo"), - ), + + // Asynchronous initialization of DataBloc + bind().toProvideAsync( + () async { + final repo = await currentScope.resolveAsync(named: "networkRepo"); + return DataBloc(repo); + }, ); } } @@ -117,7 +136,8 @@ void main() async { .openSubScope("featureScope") .installModules([FeatureModule(isMock: true)]); - final dataBloc = subScope.resolve(); + // Asynchronous instance resolution + final dataBloc = await subScope.resolveAsync(); dataBloc.data.listen((d) => print('Received data: $d'), onError: (e) => print('Error: $e'), onDone: () => print('DONE')); @@ -128,7 +148,7 @@ class DataBloc { final DataRepository _dataRepository; Stream get data => _dataController.stream; - StreamController _dataController = new StreamController.broadcast(); + StreamController _dataController = StreamController.broadcast(); DataBloc(this._dataRepository); @@ -185,6 +205,8 @@ class ApiClientImpl implements ApiClient { - [x] Main Scope and Sub Scopes - [x] Named Instance Initialization +- [x] Asynchronous Dependency Resolution +- [x] Dynamic Parameter Support for Providers ## Contributing diff --git a/cherrypick/example/bin/main.dart b/cherrypick/example/bin/main.dart index 5da6cb9..d797948 100644 --- a/cherrypick/example/bin/main.dart +++ b/cherrypick/example/bin/main.dart @@ -1,12 +1,12 @@ import 'dart:async'; -import 'package:cherrypick/cherrypick.dart'; import 'package:meta/meta.dart'; +import 'package:cherrypick/cherrypick.dart'; class AppModule extends Module { @override void builder(Scope currentScope) { - bind().withName('apiClientMock').toInstance(ApiClientMock()); - bind().withName('apiClientImpl').toInstance(ApiClientImpl()); + bind().withName("apiClientMock").toInstance(ApiClientMock()); + bind().withName("apiClientImpl").toInstance(ApiClientImpl()); } } @@ -17,60 +17,54 @@ class FeatureModule extends Module { @override void builder(Scope currentScope) { - // DataRepository remains async for demonstration - bind().withName('networkRepo').toProvideAsync( - () async { - // Using synchronous resolve for ApiClient - final apiClient = currentScope.resolve( - named: isMock ? 'apiClientMock' : 'apiClientImpl', - ); - return NetworkDataRepository(apiClient); - }, - ).singleton(); + // Using toProvideAsync for async initialization + bind().withName("networkRepo").toProvideAsync(() async { + final client = await Future.delayed( + Duration(milliseconds: 100), + () => currentScope.resolve( + named: isMock ? "apiClientMock" : "apiClientImpl")); + return NetworkDataRepository(client); + }).singleton(); - bind().toProvideAsyncWithParams( - (param) async { - final dataRepository = await currentScope.resolveAsync( - named: 'networkRepo'); - return DataBloc(dataRepository, param); + // Asynchronous initialization of DataBloc + bind().toProvideAsync( + () async { + final repo = await currentScope.resolveAsync( + named: "networkRepo"); + return DataBloc(repo); }, ); } } -Future main() async { +void main() async { final scope = openRootScope().installModules([ AppModule(), ]); final subScope = scope - .openSubScope('featureScope') + .openSubScope("featureScope") .installModules([FeatureModule(isMock: true)]); - try { - final dataBloc = await subScope.resolveAsync(params: 'PARAMETER'); - dataBloc.data.listen((d) => print('Received data: $d'), - onError: (e) => print('Error: $e'), onDone: () => print('DONE')); + // Asynchronous instance resolution + final dataBloc = await subScope.resolveAsync(); + dataBloc.data.listen((d) => print('Received data: $d'), + onError: (e) => print('Error: $e'), onDone: () => print('DONE')); - await dataBloc.fetchData(); - } catch (e) { - print('Error resolving dependency: $e'); - } + await dataBloc.fetchData(); } class DataBloc { final DataRepository _dataRepository; - final StreamController _dataController = StreamController.broadcast(); Stream get data => _dataController.stream; + final StreamController _dataController = StreamController.broadcast(); - final String param; - - DataBloc(this._dataRepository, this.param); + DataBloc(this._dataRepository); Future fetchData() async { try { - _dataController.sink.add(await _dataRepository.getData(param)); + _dataController.sink.add(await _dataRepository.getData()); } catch (e) { _dataController.sink.addError(e); } @@ -82,7 +76,7 @@ class DataBloc { } abstract class DataRepository { - Future getData(String param); + Future getData(); } class NetworkDataRepository implements DataRepository { @@ -92,42 +86,26 @@ class NetworkDataRepository implements DataRepository { NetworkDataRepository(this._apiClient); @override - Future getData(String param) async => await _apiClient.sendRequest( - url: 'www.google.com', - token: _token, - requestBody: {'type': 'data'}, - param: param); + Future 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, - String param, - }); + Future sendRequest({@required String url, String token, Map requestBody}); } class ApiClientMock implements ApiClient { @override - Future sendRequest({ - @required String? url, - String? token, - Map? requestBody, - String? param, - }) async { - return 'Local Data $param'; + Future sendRequest( + {@required String? url, String? token, Map? requestBody}) async { + return 'Local Data'; } } class ApiClientImpl implements ApiClient { @override - Future sendRequest({ - @required String? url, - String? token, - Map? requestBody, - String? param, - }) async { - return 'Network data $param'; + Future sendRequest( + {@required String? url, String? token, Map? requestBody}) async { + return 'Network data'; } }