diff --git a/CHANGELOG.md b/CHANGELOG.md index e687e78..12e2b0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## 0.3.0 + +* Migrating from flutter_test and flutter_lints to test and lints. +* Added support for Streams. +* Refactor Events behavior. +* Exceptions and code improvements. + + ## 0.2.0 * Added Mixin PostConstruct with execution after class construction. diff --git a/README.md b/README.md index d9fc19c..2bc9e88 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,14 @@ and the Flutter guide for # Dart Dependency Injection (DDI) Library +[![pub package](https://img.shields.io/pub/v/dart_ddi.svg?logo=dart&logoColor=00b9fc)](https://pub.dartlang.org/packages/dart_ddi) +[![CI](https://img.shields.io/github/actions/workflow/status/Willian199/dart_ddi/dart.yml?branch=master&logo=github-actions&logoColor=white)](https://github.com/Willian199/dart_ddi/actions) +[![Last Commits](https://img.shields.io/github/last-commit/Willian199/dart_ddi?logo=git&logoColor=white)](https://github.com/Willian199/dart_ddi/commits/master) +[![Issues](https://img.shields.io/github/issues/Willian199/dart_ddi?logo=github&logoColor=white)](https://github.com/Willian199/dart_ddi/issues) +[![Pull Requests](https://img.shields.io/github/issues-pr/Willian199/dart_ddi?logo=github&logoColor=white)](https://github.com/Willian199/dart_ddi/pulls) +[![Code size](https://img.shields.io/github/languages/code-size/Willian199/dart_ddi?logo=github&logoColor=white)](https://github.com/Willian199/dart_ddi) +[![License](https://img.shields.io/github/license/Willian199/dart_ddi?logo=open-source-initiative&logoColor=green)](https://github.com/Willian199/dart_ddi/blob/master/LICENSE) + ## Overview The Dart Dependency Injection (DDI) library is a robust and flexible dependency injection mechanism inspired by the Contexts and Dependency Injection (CDI) framework in Java and by Get_It dart package. DDI facilitates the management of object instances and their lifecycles by introducing different scopes and customization options. This documentation aims to provide an in-depth understanding of DDI's core concepts, usage, and features. @@ -49,7 +57,12 @@ Summary 4. [Firing an Event](#firing-an-event) 5. [Events Considerations](#events-considerations) 6. [Use Cases](#use-cases) -7. [API Reference](#api-reference) +7. [Stream](#stream) + 1. [Subscription](#subscription) + 2. [Closing Stream](#closing-stream) + 3. [Firing Events](#firing-events) + 4. [Retrieving Stream](#retrieving-stream) +9. [API Reference](#api-reference) 1. [registerSingleton](#registersingleton) 2. [registerApplication](#registerapplication) 3. [registerDependent](#registerdependent) @@ -258,27 +271,27 @@ The Interceptor is a powerful mechanism that provides fine-grained control over #### Example Usage ```dart - class CustomInterceptor extends DDIInterceptor { + class CustomInterceptor extends DDIInterceptor { @override - T aroundConstruct(T instance) { + BeanT aroundConstruct(BeanT instance) { // Logic to customize or replace instance creation. return CustomizedInstance(); } @override - T aroundGet(T instance) { + BeanT aroundGet(BeanT instance) { // Logic to customize the behavior of the retrieved instance. return ModifiedInstance(instance); } @override - void aroundDestroy(T instance) { + void aroundDestroy(BeanT instance) { // Logic to perform cleanup during instance destruction. // This method is optional and can be overridden as needed. } @override - void aroundDispose(T instance) { + void aroundDispose(BeanT instance) { // Logic to release resources or perform custom cleanup during instance disposal. // This method is optional and can be overridden as needed. } @@ -389,7 +402,7 @@ The Events follow a straightforward flow. Functions or methods `subscribe` to sp ### Subscribing an Event When subscribing to an event, you have the option to choose from three different types of subscriptions: `subscribe`, `subscribeAsync` and `subscribeIsolate`. -**subscribe** +#### subscribe The common subscription type, subscribe, functions as a simple callback. It allows you to respond to events in a synchronous manner, making it suitable for most scenarios. - `DDIEvent.instance.subscribe` It's the common type, working as a simples callback. @@ -420,7 +433,7 @@ DDIEvent.instance.subscribe( ); ``` -**subscribeAsync** +#### subscribeAsync The subscribeAsync type runs the callback as a Future, allowing for asynchronous event handling. Making it suitable for scenarios where asynchronous execution is needed without waiting for completion. Note that it not be possible to await this type of subscription. @@ -441,7 +454,7 @@ DDIEvent.instance.subscribeAsync( ); ``` -**subscribeIsolate** +#### subscribeIsolate The subscribeIsolate type runs the callback in a separate isolate, enabling concurrent event handling. This is particularly useful for scenarios where you want to execute the event in isolation, avoiding potential interference with the main application flow. Parameters are the same as for `subscribe`. @@ -459,6 +472,7 @@ DDIEvent.instance.subscribeIsolate( unsubscribeAfterFire: false, runAsIsolate: false, ); +``` ### Unsubscribing an Event @@ -497,6 +511,69 @@ See the considerations about [Qualifiers](#considerations). `Background Task`: Coordinate background tasks and events for efficient task execution. +# Stream + +The `DDIStream` abstract class in serves as a foundation for managing streams efficiently within the application. This class provides methods for subscribing, closing, and firing events through streams. Below are the key features and usage guidelines for the DDIStream abstract class. + +## Subscription + +Use `subscribe` to register a callback function that will be invoked when the stream emits a value. This method supports optional qualifiers, conditional registration, and automatic unsubscription after the first invocation. + +Subscribes to a stream of type `StreamTypeT`. + +- `callback`: A function to be invoked when the stream emits a value. +- `qualifier`: An optional qualifier to distinguish between different streams of the same type. +- `registerIf`: An optional function to conditionally register the subscription. +- `unsubscribeAfterFire`: If true, unsubscribes the callback after it is invoked once. + +```dart +void subscribe({ + required void Function(StreamTypeT) callback, + Object? qualifier, + bool Function()? registerIf, + bool unsubscribeAfterFire = false, +}); +``` + +## Closing Stream + +Use `close` to end the subscription to a specific stream, allowing for efficient resource management. + +Closes the subscription to a stream of type `StreamTypeT`. +- `qualifier`: An optional qualifier to distinguish between different streams of the same type. + +```dart +void close({Object? qualifier}); +``` + +## Firing Events + +Use `fire` to sends a value into the stream, triggering the subscribed callbacks. You can specify the target stream using the optional qualifier. + +Fires a value into the stream of type `StreamTypeT`. +- `value`: The value to be emitted into the stream. +- `qualifier`: An optional qualifier to distinguish between different streams of the same type. + +```dart +void fire({ + required StreamTypeT value, + Object? qualifier, +}); +``` + +## Retrieving Stream + +Use `getStream` to obtain a stream, providing access for further interactions. The optional qualifier allows you to retrieve a specific stream. + +Retrieves a stream of type `StreamTypeT`. +- `qualifier`: An optional qualifier to distinguish between different streams of the same type. + +```dart +Stream getStream( + {Object? qualifier}); + +``` + # API Reference ## registerSingleton @@ -504,28 +581,28 @@ See the considerations about [Qualifiers](#considerations). Registers a singleton instance. The `clazzRegister` parameter is a factory function to create the instance. Optional parameters allow customization of the instance's behavior and lifecycle. ```dart -void registerSingleton( - T Function() clazzRegister, { +void registerSingleton( + BeanT Function() clazzRegister, { Object? qualifier, void Function()? postConstruct, - List? decorators, - List Function()>? interceptors, + List? decorators, + List Function()>? interceptors, bool Function()? registerIf, bool destroyable = true, }); ``` -## registerApplication +## registerApplication Registers an application-scoped instance. The instance is created when first used and reused afterward. ```dart -void registerApplication( - T Function() clazzRegister, { +void registerApplication( + BeanT Function() clazzRegister, { Object? qualifier, void Function()? postConstruct, - List? decorators, - List Function()>? interceptors, + List? decorators, + List Function()>? interceptors, bool Function()? registerIf, bool destroyable = true, }); @@ -536,12 +613,12 @@ void registerApplication( Registers a dependent instance. A new instance is created every time it is used. ```dart -void registerDependent( - T Function() clazzRegister, { +void registerDependent( + BeanT Function() clazzRegister, { Object? qualifier, void Function()? postConstruct, - List? decorators, - List Function()>? interceptors, + List? decorators, + List Function()>? interceptors, bool Function()? registerIf, bool destroyable = true, }); @@ -552,12 +629,12 @@ void registerDependent( Registers a session-scoped instance. The instance is tied to a specific session. ```dart -void registerSession( - T Function() clazzRegister, { +void registerSession( + BeanT Function() clazzRegister, { Object? qualifier, void Function()? postConstruct, - List? decorators, - List Function()>? interceptors, + List? decorators, + List Function()>? interceptors, bool Function()? registerIf, bool destroyable = true, }); @@ -568,12 +645,12 @@ void registerSession( Registers an Object values as instance. The `register` parameter is the value shared across de application. ```dart -void registerObject({ +void registerObject({ required Object qualifier, - required T register, + required BeanT register, void Function()? postConstruct, - List? decorators, - List Function()>? interceptors, + List? decorators, + List Function()>? interceptors, bool Function()? registerIf, bool destroyable = true, }); @@ -581,26 +658,26 @@ void registerObject({ ## get -Retrieves an instance of type T from the appropriate scope. You can provide a `qualifier` to distinguish between instances of the same type. +Retrieves an instance of type BeanT from the appropriate scope. You can provide a `qualifier` to distinguish between instances of the same type. ```dart -T get({Object? qualifier}); +BeanT get({Object? qualifier}); ``` ## getByType -Retrieves all instance identifiers of type T from each scope. +Retrieves all instance identifiers of type BeanT from each scope. ```dart -List getByType(); +List getByType(); ``` ## call -A shorthand for get(), allowing a more concise syntax for obtaining instances. +A shorthand for get(), allowing a more concise syntax for obtaining instances. ```dart -T call(); +BeanT call(); ``` ## destroy @@ -608,15 +685,15 @@ T call(); Destroy an instance from the container. Useful for manual cleanup. ```dart -void destroy({Object? qualifier}); +void destroy({Object? qualifier}); ``` ## destroyByType -Destroy all instance with type `T`. +Destroy all instance with type `BeanT`. ```dart -void destroyByType(); +void destroyByType(); ``` ## dispose @@ -624,15 +701,15 @@ void destroyByType(); Disposes of an instance, invoking any cleanup logic. This is particularly useful for instances with resources that need to be released. Only applied to Application and Session Scopes ```dart -void dispose({Object? qualifier}); +void dispose({Object? qualifier}); ``` ## disposeByType -Disposes all instance with type `T`. Only applied to Application and Session Scopes +Disposes all instance with type `BeanT`. Only applied to Application and Session Scopes ```dart -void disposeByType(); +void disposeByType(); ``` ## addDecorator @@ -641,21 +718,21 @@ This provides a dynamic way to enhance the behavior of registered instances by a When using the addDecorator method, keep in mind the order of execution, scope considerations, and the fact that instances already obtained remain unaffected. ```dart -void addDecorator(List decorators, {Object? qualifier}); +void addDecorator(List decorators, {Object? qualifier}); ``` ## addInterceptor This feature allows you to dynamically influence the instantiation, retrieval, destruction, and disposal of instances by adding custom interceptors. The `addInterceptor` method enables you to associate specific interceptors with particular types. ```dart -void addInterceptor(List Function()> interceptors, {Object? qualifier}); +void addInterceptor(List Function()> interceptors, {Object? qualifier}); ``` ## refreshObject Enables the dynamic refreshing of an object within the Object Scope. Use it to update the existing object without affecting instances already obtained. ```dart -void refreshObject({required Object qualifier, required T register, +void refreshObject({required Object qualifier, required BeanT register, }); ``` diff --git a/example/lib/common/cubit_features/cubit_event_listener.dart b/example/lib/common/cubit_features/cubit_event_listener.dart index ba11634..88f9c91 100644 --- a/example/lib/common/cubit_features/cubit_event_listener.dart +++ b/example/lib/common/cubit_features/cubit_event_listener.dart @@ -3,8 +3,9 @@ import 'package:flutter_bloc/flutter_bloc.dart'; abstract class CubitListener extends Cubit { - CubitListener({required State initialState}) : super(initialState) { - DDIEvent.instance.subscribeAsync(onEvent); + CubitListener(super.initialState) { + //DDIEvent.instance.subscribeAsync(onEvent); + DDIStream.instance.subscribe(callback: onEvent); } void onEvent(Listen listen); @@ -12,7 +13,8 @@ abstract class CubitListener @override Future close() async { print('cubit fechado'); - DDIEvent.instance.unsubscribe(onEvent); + // DDIEvent.instance.unsubscribe(onEvent); + DDIStream.instance.close(); super.close(); } diff --git a/example/lib/common/cubit_features/cubit_event_sender.dart b/example/lib/common/cubit_features/cubit_event_sender.dart index 3d01e53..333367e 100644 --- a/example/lib/common/cubit_features/cubit_event_sender.dart +++ b/example/lib/common/cubit_features/cubit_event_sender.dart @@ -8,7 +8,8 @@ class CubitSender extends Cubit { void fire(State state) { debugPrint('emitindo evento $this'); - DDIEvent.instance.fire(state); + //DDIEvent.instance.fire(state); + DDIStream.instance.fire(value: state); super.emit(state); } diff --git a/example/lib/modules/item/cubit/item_cubit.dart b/example/lib/modules/item/cubit/item_cubit.dart index a823cc0..4327455 100644 --- a/example/lib/modules/item/cubit/item_cubit.dart +++ b/example/lib/modules/item/cubit/item_cubit.dart @@ -6,7 +6,7 @@ import 'package:perfumei/modules/item/state/tab_state.dart'; class TabCubit extends CubitListener { TabCubit() : super( - initialState: TabState( + TabState( page: NotasEnum.TOPO.posicao, tabSelecionada: {NotasEnum.TOPO}, ), diff --git a/lib/dart_ddi.dart b/lib/dart_ddi.dart index 856f77c..c71e025 100644 --- a/lib/dart_ddi.dart +++ b/lib/dart_ddi.dart @@ -2,6 +2,7 @@ library dart_ddi; export 'src/core/bean/dart_ddi.dart' show DDI; export 'src/core/event/dart_ddi_event.dart' show DDIEvent; +export 'src/core/stream/dart_ddi_stream.dart' show DDIStream; export 'src/features/ddi_interceptor.dart'; export 'src/features/post_construct.dart'; export 'src/features/pre_destroy.dart'; diff --git a/lib/src/core/bean/dart_ddi.dart b/lib/src/core/bean/dart_ddi.dart index 0742577..c8b505e 100644 --- a/lib/src/core/bean/dart_ddi.dart +++ b/lib/src/core/bean/dart_ddi.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:dart_ddi/src/data/factory_clazz.dart'; import 'package:dart_ddi/src/enum/scopes.dart'; +import 'package:dart_ddi/src/exception/bean_destroyed.dart'; import 'package:dart_ddi/src/exception/bean_not_found.dart'; import 'package:dart_ddi/src/exception/circular_detection.dart'; import 'package:dart_ddi/src/exception/duplicated_bean.dart'; @@ -161,9 +162,9 @@ abstract class DDI { /// - `qualifier`: Optional qualifier name to distinguish between different instances of the same type. BeanT get({Object? qualifier}); - /// Retrieves a list of keys associated with objects of a specific type `T`. + /// Retrieves a list of keys associated with objects of a specific type BeanT`. /// - /// This method allows you to obtain all keys (qualifier names) that have been used to register objects of the specified type `T`. + /// This method allows you to obtain all keys (qualifier names) that have been used to register objects of the specified type `BeanT`. List getByType(); /// Gets an instance of the registered class in [DDI]. @@ -179,7 +180,7 @@ abstract class DDI { /// Removes all the instance registered as Session Scope. void destroyAllSession(); - /// Removes all the instance registered as type `T`. + /// Removes all the instance registered as type `BeanT`. void destroyByType(); /// Disposes of the instance of the registered class in [DDI]. @@ -190,7 +191,7 @@ abstract class DDI { /// Disposes all the instance registered as Session Scope. void disposeAllSession(); - /// Disposes all the instance registered as type `T`. + /// Disposes all the instance registered as type `BeanT`. void disposeByType(); /// Allows to dynamically add a Decorators. diff --git a/lib/src/core/bean/dart_ddi_impl.dart b/lib/src/core/bean/dart_ddi_impl.dart index 85ee456..571e932 100644 --- a/lib/src/core/bean/dart_ddi_impl.dart +++ b/lib/src/core/bean/dart_ddi_impl.dart @@ -24,13 +24,8 @@ class _DDIImpl implements DDI { final Object effectiveQualifierName = qualifier ?? BeanT; if (_beans[effectiveQualifierName] != null) { - final cause = - 'Is already registered a instance with Type ${effectiveQualifierName.toString()}'; - if (!_debug) { - throw DuplicatedBean(cause); - } - // ignore: avoid_print - print(cause); + _validateDuplicated(effectiveQualifierName); + return; } @@ -70,17 +65,16 @@ class _DDIImpl implements DDI { bool Function()? registerIf, bool destroyable = true, }) { - if (registerIf?.call() ?? true) { - _register( - clazzRegister: clazzRegister, - scopeType: Scopes.application, - qualifier: qualifier, - postConstruct: postConstruct, - decorators: decorators, - interceptors: interceptors, - destroyable: destroyable, - ); - } + _register( + clazzRegister: clazzRegister, + scopeType: Scopes.application, + qualifier: qualifier, + postConstruct: postConstruct, + decorators: decorators, + interceptors: interceptors, + destroyable: destroyable, + registerIf: registerIf, + ); } @override @@ -93,17 +87,16 @@ class _DDIImpl implements DDI { bool Function()? registerIf, bool destroyable = true, }) { - if (registerIf?.call() ?? true) { - _register( - clazzRegister: clazzRegister, - scopeType: Scopes.session, - qualifier: qualifier, - postConstruct: postConstruct, - decorators: decorators, - interceptors: interceptors, - destroyable: destroyable, - ); - } + _register( + clazzRegister: clazzRegister, + scopeType: Scopes.session, + qualifier: qualifier, + postConstruct: postConstruct, + decorators: decorators, + interceptors: interceptors, + destroyable: destroyable, + registerIf: registerIf, + ); } @override @@ -116,17 +109,16 @@ class _DDIImpl implements DDI { bool Function()? registerIf, bool destroyable = true, }) { - if (registerIf?.call() ?? true) { - _register( - clazzRegister: clazzRegister, - scopeType: Scopes.dependent, - qualifier: qualifier, - postConstruct: postConstruct, - decorators: decorators, - interceptors: interceptors, - destroyable: destroyable, - ); - } + _register( + clazzRegister: clazzRegister, + scopeType: Scopes.dependent, + qualifier: qualifier, + postConstruct: postConstruct, + decorators: decorators, + interceptors: interceptors, + destroyable: destroyable, + registerIf: registerIf, + ); } void _register({ @@ -137,29 +129,36 @@ class _DDIImpl implements DDI { void Function()? postConstruct, List? decorators, List Function()>? interceptors, + bool Function()? registerIf, }) { - final Object effectiveQualifierName = qualifier ?? BeanT; + if (registerIf?.call() ?? true) { + final Object effectiveQualifierName = qualifier ?? BeanT; - if (_beans[effectiveQualifierName] != null) { - final cause = - 'Is already registered a instance with Type ${effectiveQualifierName.toString()}'; - if (!_debug) { - throw DuplicatedBean(cause); + if (_beans[effectiveQualifierName] != null) { + _validateDuplicated(effectiveQualifierName); + return; } - // ignore: avoid_print - print(cause); - return; + + _beans[effectiveQualifierName] = FactoryClazz( + clazzRegister: clazzRegister, + type: BeanT, + postConstruct: postConstruct, + decorators: decorators, + interceptors: interceptors, + scopeType: scopeType, + destroyable: destroyable, + ); } + } - _beans[effectiveQualifierName] = FactoryClazz( - clazzRegister: clazzRegister, - type: BeanT, - postConstruct: postConstruct, - decorators: decorators, - interceptors: interceptors, - scopeType: scopeType, - destroyable: destroyable, - ); + void _validateDuplicated(Object effectiveQualifierName) { + if (!_debug) { + throw DuplicatedBean(effectiveQualifierName.toString()); + } else { + // ignore: avoid_print + print( + 'Is already registered a instance with Type ${effectiveQualifierName.toString()}'); + } } @override @@ -176,13 +175,7 @@ class _DDIImpl implements DDI { final Object effectiveQualifierName = qualifier ?? BeanT; if (_beans[effectiveQualifierName] != null) { - final cause = - 'Is already registered a instance with Type ${effectiveQualifierName.toString()}'; - if (!_debug) { - throw DuplicatedBean(cause); - } - // ignore: avoid_print - print(cause); + _validateDuplicated(effectiveQualifierName); return; } @@ -215,36 +208,37 @@ class _DDIImpl implements DDI { return get(); } - BeanT _getSingleton(FactoryClazz factoryClazz) { - assert(factoryClazz.clazzInstance != null, - 'The Singleton Type ${BeanT.runtimeType.toString()} is destroyed'); - - if (factoryClazz.interceptors != null) { - for (final interceptor in factoryClazz.interceptors!) { - factoryClazz.clazzInstance = - interceptor.call().aroundGet(factoryClazz.clazzInstance!); + BeanT _getSingleton( + FactoryClazz factoryClazz, Object effectiveQualifierName) { + if (factoryClazz.clazzInstance case var clazz?) { + if (factoryClazz.interceptors case final inter?) { + for (final interceptor in inter) { + clazz = interceptor.call().aroundGet(clazz); + } } + + return clazz; } - return factoryClazz.clazzInstance!; + throw BeanDestroyed(effectiveQualifierName.toString()); } BeanT _getAplication( - FactoryClazz factoryClazz, effectiveQualifierName) { - BeanT? applicationClazz = factoryClazz.clazzInstance; + FactoryClazz factoryClazz, Object effectiveQualifierName) { + late BeanT applicationClazz; if (factoryClazz.clazzInstance == null) { applicationClazz = factoryClazz.clazzRegister!.call(); - if (factoryClazz.interceptors != null) { - for (final interceptor in factoryClazz.interceptors!) { + if (factoryClazz.interceptors case final inter?) { + for (final interceptor in inter) { applicationClazz = - interceptor.call().aroundConstruct(applicationClazz!); + interceptor.call().aroundConstruct(applicationClazz); } } - applicationClazz = _executarDecorators( - applicationClazz!, factoryClazz.decorators); + applicationClazz = + _executarDecorators(applicationClazz, factoryClazz.decorators); factoryClazz.postConstruct?.call(); @@ -253,22 +247,24 @@ class _DDIImpl implements DDI { } factoryClazz.clazzInstance = applicationClazz; + } else { + applicationClazz = factoryClazz.clazzInstance!; } - if (factoryClazz.interceptors != null) { - for (final interceptor in factoryClazz.interceptors!) { - applicationClazz = interceptor.call().aroundGet(applicationClazz!); + if (factoryClazz.interceptors case final inter?) { + for (final interceptor in inter) { + applicationClazz = interceptor.call().aroundGet(applicationClazz); } } - return applicationClazz!; + return applicationClazz; } BeanT _getDependent(FactoryClazz factoryClazz) { BeanT dependentClazz = factoryClazz.clazzRegister!.call(); - if (factoryClazz.interceptors != null) { - for (final interceptor in factoryClazz.interceptors!) { + if (factoryClazz.interceptors case final inter?) { + for (final interceptor in inter) { dependentClazz = interceptor.call().aroundConstruct(dependentClazz); } } @@ -282,8 +278,8 @@ class _DDIImpl implements DDI { dependentClazz.onPostConstruct(); } - if (factoryClazz.interceptors != null) { - for (final interceptor in factoryClazz.interceptors!) { + if (factoryClazz.interceptors case final inter?) { + for (final interceptor in inter) { dependentClazz = interceptor.call().aroundGet(dependentClazz); } } @@ -295,27 +291,23 @@ class _DDIImpl implements DDI { BeanT get({Object? qualifier}) { final Object effectiveQualifierName = qualifier ?? BeanT; - final FactoryClazz? factoryClazz = - _beans[effectiveQualifierName] as FactoryClazz?; - - if (factoryClazz == null) { - throw BeanNotFound( - 'No Instance with Type ${effectiveQualifierName.toString()} is found.'); + if (_beans[effectiveQualifierName] + case final FactoryClazz factory?) { + return runZoned( + () { + return _getScoped(factory, effectiveQualifierName); + }, + zoneValues: {_resolutionKey: >{}}, + ); } - return runZoned( - () { - return _getScoped(factoryClazz, effectiveQualifierName); - }, - zoneValues: {_resolutionKey: >{}}, - ); + throw BeanNotFound(effectiveQualifierName.toString()); } BeanT _getScoped( FactoryClazz factoryClazz, Object effectiveQualifierName) { if (_resolutionMap[effectiveQualifierName]?.isNotEmpty ?? false) { - throw CircularDetection( - 'Circular Detection found for Instance Type ${effectiveQualifierName.toString()}!!!'); + throw CircularDetection(effectiveQualifierName.toString()); } _resolutionMap[effectiveQualifierName] = [ @@ -323,10 +315,11 @@ class _DDIImpl implements DDI { effectiveQualifierName ]; - BeanT result; try { - result = switch (factoryClazz.scopeType) { - Scopes.singleton || Scopes.object => _getSingleton(factoryClazz), + return switch (factoryClazz.scopeType) { + Scopes.singleton || + Scopes.object => + _getSingleton(factoryClazz, effectiveQualifierName), Scopes.dependent => _getDependent(factoryClazz), Scopes.application || Scopes.session => @@ -335,8 +328,6 @@ class _DDIImpl implements DDI { } finally { _resolutionMap[effectiveQualifierName]?.removeLast(); } - - return result; } @override @@ -350,7 +341,9 @@ class _DDIImpl implements DDI { } BeanT _executarDecorators( - BeanT clazz, List? decorators) { + BeanT clazz, + List? decorators, + ) { if (decorators != null) { for (final decorator in decorators) { clazz = decorator(clazz); @@ -368,21 +361,18 @@ class _DDIImpl implements DDI { } void _destroy(effectiveQualifierName) { - final FactoryClazz? factoryClazz = - _beans[effectiveQualifierName] as FactoryClazz?; - - if (factoryClazz != null && factoryClazz.destroyable) { - if (factoryClazz.clazzInstance != null) { - if (factoryClazz.interceptors != null) { - for (final interceptor in factoryClazz.interceptors!) { - interceptor - .call() - .aroundDestroy(factoryClazz.clazzInstance as BeanT); + if (_beans[effectiveQualifierName] case final factoryClazz? + when factoryClazz.destroyable) { + //Only destroy if destroyable was registered with true + if (factoryClazz.clazzInstance case final clazz?) { + if (factoryClazz.interceptors case final inter?) { + for (final interceptor in inter) { + interceptor.call().aroundDestroy(clazz); } } - if (factoryClazz.clazzInstance is PreDestroy) { - (factoryClazz.clazzInstance as PreDestroy).onPreDestroy(); + if (clazz is PreDestroy) { + clazz.onPreDestroy(); } } @@ -392,13 +382,10 @@ class _DDIImpl implements DDI { @override void destroyAllSession() { - _destroyAll(Scopes.session); - } - - void _destroyAll(Scopes scope) { final keys = _beans.entries .where((element) => - element.value.scopeType == scope && element.value.destroyable) + element.value.scopeType == Scopes.session && + element.value.destroyable) .map((e) => e.key) .toList(); @@ -420,10 +407,10 @@ class _DDIImpl implements DDI { void dispose({Object? qualifier}) { final Object effectiveQualifierName = qualifier ?? BeanT; - final FactoryClazz? factoryClazz = - _beans[effectiveQualifierName] as FactoryClazz?; - - if (factoryClazz != null) { + if (_beans[effectiveQualifierName] + case final FactoryClazz factoryClazz?) { + //Singleton e Object only can destroy + //Dependent doesn't have instance switch (factoryClazz.scopeType) { case Scopes.application: case Scopes.session: @@ -437,17 +424,18 @@ class _DDIImpl implements DDI { /// Dispose only clean the class Instance void _disposeBean( - FactoryClazz? factoryClazz, Object effectiveQualifierName) { - if (factoryClazz != null) { - if (factoryClazz.clazzInstance != null && - factoryClazz.interceptors != null) { - for (final interceptor in factoryClazz.interceptors!) { - interceptor.call().aroundDispose(factoryClazz.clazzInstance as BeanT); - } + FactoryClazz factoryClazz, + Object effectiveQualifierName, + ) { + if (factoryClazz.clazzInstance != null && + factoryClazz.interceptors != null) { + //Call aroundDispose before reset the clazzInstance + for (final interceptor in factoryClazz.interceptors!) { + interceptor().aroundDispose(factoryClazz.clazzInstance as BeanT); } - - factoryClazz.clazzInstance = null; } + + factoryClazz.clazzInstance = null; } @override @@ -475,16 +463,16 @@ class _DDIImpl implements DDI { @override void addDecorator( - List decorators, - {Object? qualifier}) { + List decorators, { + Object? qualifier, + }) { final Object effectiveQualifierName = qualifier ?? BeanT; final FactoryClazz? factoryClazz = _beans[effectiveQualifierName] as FactoryClazz?; if (factoryClazz == null) { - throw BeanNotFound( - 'No Instance with Type ${effectiveQualifierName.toString()} is found.'); + throw BeanNotFound(effectiveQualifierName.toString()); } switch (factoryClazz.scopeType) { @@ -497,57 +485,37 @@ class _DDIImpl implements DDI { //Application and Session Scopes may have a instance created case Scopes.application: case Scopes.session: - if (factoryClazz.clazzInstance != null) { - factoryClazz.clazzInstance = _executarDecorators( - factoryClazz.clazzInstance!, decorators); + if (factoryClazz.clazzInstance case final clazz?) { + factoryClazz.clazzInstance = + _executarDecorators(clazz, decorators); } - factoryClazz.decorators = _orderDecorator(decorators, factoryClazz); - - break; //Dependent Scopes always require a new instance case Scopes.dependent: - factoryClazz.decorators = _orderDecorator(decorators, factoryClazz); - break; - } - } - - List _orderDecorator( - List decorators, - FactoryClazz factoryClazz) { - List updatedDecorators = []; - - if (factoryClazz.decorators != null) { - updatedDecorators = decorators.reversed.toList(); - - updatedDecorators.addAll(factoryClazz.decorators!.reversed.toList()); + factoryClazz.decorators = [ + ...factoryClazz.decorators ?? [], + ...decorators + ]; - updatedDecorators = updatedDecorators.reversed.toList(); - } else { - updatedDecorators = decorators; + break; } - - return updatedDecorators; } @override void addInterceptor( - List Function()> interceptors, - {Object? qualifier}) { + List Function()> interceptors, { + Object? qualifier, + }) { final Object effectiveQualifierName = qualifier ?? BeanT; - final FactoryClazz? factoryClazz = - _beans[effectiveQualifierName] as FactoryClazz?; - - if (factoryClazz == null) { - throw BeanNotFound( - 'No Instance with Type ${effectiveQualifierName.toString()} is found.'); - } - - if (factoryClazz.interceptors == null) { - factoryClazz.interceptors = interceptors; + if (_beans[effectiveQualifierName] + case final FactoryClazz factoryClazz?) { + factoryClazz.interceptors = [ + ...factoryClazz.interceptors ?? [], + ...interceptors + ]; } else { - factoryClazz.interceptors?.addAll(interceptors); + throw BeanNotFound(effectiveQualifierName.toString()); } } @@ -561,8 +529,7 @@ class _DDIImpl implements DDI { _beans[effectiveQualifierName] as FactoryClazz?; if (factoryClazz == null) { - throw BeanNotFound( - 'No registered with Type ${effectiveQualifierName.toString()} is found.'); + throw BeanNotFound(effectiveQualifierName.toString()); } if (factoryClazz.interceptors != null) { diff --git a/lib/src/core/event/dart_ddi_event.dart b/lib/src/core/event/dart_ddi_event.dart index 81590f4..c108e4d 100644 --- a/lib/src/core/event/dart_ddi_event.dart +++ b/lib/src/core/event/dart_ddi_event.dart @@ -1,5 +1,6 @@ import 'package:dart_ddi/src/data/event.dart'; import 'package:dart_ddi/src/enum/event_mode.dart'; +import 'package:dart_ddi/src/exception/event_not_found.dart'; part 'dart_ddi_event_impl.dart'; diff --git a/lib/src/core/event/dart_ddi_event_impl.dart b/lib/src/core/event/dart_ddi_event_impl.dart index 806ac00..bc374bf 100644 --- a/lib/src/core/event/dart_ddi_event_impl.dart +++ b/lib/src/core/event/dart_ddi_event_impl.dart @@ -107,12 +107,9 @@ class _DDIEventImpl implements DDIEvent { }) { final effectiveQualifierName = qualifier ?? EventTypeT; - //Without the cast, removeWhere fails beacause the type is Event - final eventsList = - _events[effectiveQualifierName]?.cast>(); - - if (eventsList != null) { - eventsList.removeWhere( + if (_events[effectiveQualifierName] case final eventsList? + when eventsList.isNotEmpty) { + eventsList.cast>().removeWhere( (e) => e.allowUnsubscribe && e.event.hashCode == event.hashCode); } } @@ -121,13 +118,12 @@ class _DDIEventImpl implements DDIEvent { void fire(EventTypeT value, {Object? qualifier}) { final effectiveQualifierName = qualifier ?? EventTypeT; - final eventsList = - _events[effectiveQualifierName]?.cast>(); - - if (eventsList != null) { + if (_events[effectiveQualifierName] case final eventsList? + when eventsList.isNotEmpty) { final eventsToRemove = >[]; - for (final Event event in eventsList) { + for (final Event event + in eventsList.cast>()) { event.mode.execute(event.event, value); if (event.unsubscribeAfterFire) { @@ -138,6 +134,8 @@ class _DDIEventImpl implements DDIEvent { for (final Event event in eventsToRemove) { _events[effectiveQualifierName]?.remove(event); } + } else { + throw EventNotFound(effectiveQualifierName.toString()); } } } diff --git a/lib/src/core/stream/dart_ddi_stream.dart b/lib/src/core/stream/dart_ddi_stream.dart new file mode 100644 index 0000000..475e42a --- /dev/null +++ b/lib/src/core/stream/dart_ddi_stream.dart @@ -0,0 +1,45 @@ +import 'package:dart_ddi/src/core/stream/dart_ddi_stream_core.dart'; +import 'package:dart_ddi/src/exception/stream_not_found.dart'; + +part 'dart_ddi_stream_manager.dart'; + +abstract final class DDIStream { + /// Creates the shared instance of the [DDIStream] class. + static final DDIStream _instance = _DDIStreamManager(); + + /// Gets the shared instance of the [DDIStream] class. + static DDIStream get instance => _instance; + + /// Subscribes to a stream of type [StreamTypeT]. + /// + /// - `callback`: A callback function to be invoked when the stream emits a value. + /// - `qualifier`: An optional qualifier to distinguish between different streams of the same type. + /// - `registerIf`: An optional function to conditionally register the subscription. + /// - `unsubscribeAfterFire`: If set to true, unsubscribes the callback after it is invoked once. + void subscribe({ + required void Function(StreamTypeT) callback, + Object? qualifier, + bool Function()? registerIf, + bool unsubscribeAfterFire = false, + }); + + /// Closes the subscription to a stream of type [StreamTypeT]. + /// + /// - `qualifier`: An optional qualifier to specify the stream to be closed. + void close({Object? qualifier}); + + /// Fires a value into the stream of type [StreamTypeT]. + /// + /// - `value`: The value to be emitted into the stream. + /// - `qualifier`: An optional qualifier to specify the target stream. + void fire({ + required StreamTypeT value, + Object? qualifier, + }); + + /// Retrieves a stream of type [StreamTypeT]. + /// + /// - `qualifier`: An optional qualifier to specify the desired stream. + Stream getStream( + {Object? qualifier}); +} diff --git a/lib/src/core/stream/dart_ddi_stream_core.dart b/lib/src/core/stream/dart_ddi_stream_core.dart new file mode 100644 index 0000000..bb009ed --- /dev/null +++ b/lib/src/core/stream/dart_ddi_stream_core.dart @@ -0,0 +1,34 @@ +import 'dart:async'; + +import 'package:dart_ddi/src/core/stream/dart_ddi_stream.dart'; + +class DDIStreamCore { + final StreamController _streamController = + StreamController.broadcast(); + + void subscribe( + void Function(StreamTypeT) callback, { + bool unsubscribeAfterFire = false, + Object? qualifier, + }) { + _streamController.stream.listen((StreamTypeT value) { + callback(value); + + if (unsubscribeAfterFire) { + DDIStream.instance.close(qualifier: qualifier); + } + }); + } + + void fire(StreamTypeT value) { + _streamController.add(value); + } + + void close() { + _streamController.close(); + } + + Stream getStream() { + return _streamController.stream; + } +} diff --git a/lib/src/core/stream/dart_ddi_stream_manager.dart b/lib/src/core/stream/dart_ddi_stream_manager.dart new file mode 100644 index 0000000..19500bb --- /dev/null +++ b/lib/src/core/stream/dart_ddi_stream_manager.dart @@ -0,0 +1,69 @@ +part of 'dart_ddi_stream.dart'; + +final class _DDIStreamManager implements DDIStream { + final Map> _streamMap = {}; + + @override + void close({Object? qualifier}) { + final Object effectiveQualifierName = qualifier ?? StreamTypeT; + + if (_streamMap[effectiveQualifierName] case final str?) { + str.close(); + + _streamMap.remove(effectiveQualifierName); + } else { + throw StreamNotFound(effectiveQualifierName.toString()); + } + } + + @override + void fire({ + required StreamTypeT value, + Object? qualifier, + }) { + _getStream(qualifier: qualifier).fire(value); + } + + @override + void subscribe({ + required void Function(StreamTypeT) callback, + Object? qualifier, + bool Function()? registerIf, + bool unsubscribeAfterFire = false, + }) { + final Object effectiveQualifierName = qualifier ?? StreamTypeT; + + if (registerIf?.call() ?? true) { + if (!_streamMap.containsKey(effectiveQualifierName)) { + _streamMap[effectiveQualifierName] = DDIStreamCore(); + } + + (_streamMap[effectiveQualifierName] as DDIStreamCore) + .subscribe( + callback, + unsubscribeAfterFire: unsubscribeAfterFire, + qualifier: qualifier, + ); + } + } + + @override + Stream getStream({ + Object? qualifier, + }) { + return _getStream(qualifier: qualifier).getStream(); + } + + DDIStreamCore _getStream({ + Object? qualifier, + }) { + final Object effectiveQualifierName = qualifier ?? StreamTypeT; + + final DDIStreamCore? stream = _streamMap[effectiveQualifierName]; + if (stream == null) { + throw StreamNotFound(effectiveQualifierName.toString()); + } + + return stream as DDIStreamCore; + } +} diff --git a/lib/src/exception/bean_destroyed.dart b/lib/src/exception/bean_destroyed.dart new file mode 100644 index 0000000..16df936 --- /dev/null +++ b/lib/src/exception/bean_destroyed.dart @@ -0,0 +1,9 @@ +class BeanDestroyed implements Exception { + const BeanDestroyed(this.type); + final String type; + + @override + String toString() { + return 'The Singleton Type $type is destroyed'; + } +} diff --git a/lib/src/exception/bean_not_found.dart b/lib/src/exception/bean_not_found.dart index 0912ab0..15ed80b 100644 --- a/lib/src/exception/bean_not_found.dart +++ b/lib/src/exception/bean_not_found.dart @@ -1,4 +1,9 @@ class BeanNotFound implements Exception { const BeanNotFound(this.cause); final String cause; + + @override + String toString() { + return 'No Instance with Type $cause is found.'; + } } diff --git a/lib/src/exception/circular_detection.dart b/lib/src/exception/circular_detection.dart index d71cb88..e9aac0a 100644 --- a/lib/src/exception/circular_detection.dart +++ b/lib/src/exception/circular_detection.dart @@ -1,4 +1,9 @@ class CircularDetection implements Exception { const CircularDetection(this.cause); final String cause; + + @override + String toString() { + return 'Circular Detection found for Instance Type $cause !!!'; + } } diff --git a/lib/src/exception/duplicated_bean.dart b/lib/src/exception/duplicated_bean.dart index 6abdab2..cabcfaa 100644 --- a/lib/src/exception/duplicated_bean.dart +++ b/lib/src/exception/duplicated_bean.dart @@ -1,4 +1,9 @@ class DuplicatedBean implements Exception { - const DuplicatedBean(this.cause); - final String cause; + const DuplicatedBean(this.type); + final String type; + + @override + String toString() { + return 'Is already registered a instance with Type $type'; + } } diff --git a/lib/src/exception/event_not_found.dart b/lib/src/exception/event_not_found.dart new file mode 100644 index 0000000..d91c24f --- /dev/null +++ b/lib/src/exception/event_not_found.dart @@ -0,0 +1,9 @@ +class EventNotFound implements Exception { + const EventNotFound(this.cause); + final String cause; + + @override + String toString() { + return 'No Event found with Type $cause.'; + } +} diff --git a/lib/src/exception/stream_not_found.dart b/lib/src/exception/stream_not_found.dart new file mode 100644 index 0000000..b16a125 --- /dev/null +++ b/lib/src/exception/stream_not_found.dart @@ -0,0 +1,9 @@ +class StreamNotFound implements Exception { + const StreamNotFound(this.cause); + final String cause; + + @override + String toString() { + return 'No Stream found with Type $cause.'; + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 95db8c4..036d4c5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: dart_ddi description: "A Dependency Injection package, with Qualifier, Decorators, Interceptors, Events and more. Inspired by Java CDI and get_it." -version: 0.2.0 +version: 0.3.0 homepage: https://github.com/Willian199/dart_ddi environment: diff --git a/test/dart_ddi_test.dart b/test/dart_ddi_test.dart index d244de7..8547523 100644 --- a/test/dart_ddi_test.dart +++ b/test/dart_ddi_test.dart @@ -11,6 +11,7 @@ import 'beans_test/register_if_test.dart'; import 'beans_test/session_test.dart'; import 'beans_test/singleton_test.dart'; import 'event_test/event_test.dart'; +import 'stream_test/stream_test.dart'; void main() { //Basic Tests, with consists in register, get, dispose, remove @@ -36,4 +37,7 @@ void main() { //Events eventTest(); + + //Streams + streamTest(); } diff --git a/test/event_test/event_test.dart b/test/event_test/event_test.dart index 5f242c1..9e91511 100644 --- a/test/event_test/event_test.dart +++ b/test/event_test/event_test.dart @@ -1,4 +1,7 @@ +import 'dart:async'; + import 'package:dart_ddi/dart_ddi.dart'; +import 'package:dart_ddi/src/exception/event_not_found.dart'; import 'package:test/test.dart'; void eventTest() { @@ -17,9 +20,8 @@ void eventTest() { DDIEvent.instance.unsubscribe(eventFunction, qualifier: 'testQualifier'); - DDIEvent.instance.fire(1, qualifier: 'testQualifier'); - - expect(localValue, 1); + expect(() => DDIEvent.instance.fire(1, qualifier: 'testQualifier'), + throwsA(isA())); }); test('subscribe adds event to the correct type', () { @@ -36,9 +38,7 @@ void eventTest() { DDIEvent.instance.unsubscribe(eventFunction); - DDIEvent.instance.fire(1); - - expect(localValue, 1); + expect(() => DDIEvent.instance.fire(1), throwsA(isA())); }); test('subscribe adds event and remove after fire', () { @@ -54,9 +54,7 @@ void eventTest() { expect(localValue, 1); - DDIEvent.instance.fire(1); - - expect(localValue, 1); + expect(() => DDIEvent.instance.fire(1), throwsA(isA())); }); test('subscribe adds two event with priority', () { @@ -187,7 +185,7 @@ void eventTest() { throwsA(const TypeMatcher())); }); - test('subscribe a isolate event', () { + test('subscribe a isolate event', () async { int localValue = 0; void eventFunction(int value) { print('Isolate event'); @@ -205,5 +203,120 @@ void eventTest() { DDIEvent.instance.unsubscribe(eventFunction, qualifier: 'testQualifier'); }); + + test('subscribeAsync and fire event asynchronously', () async { + final Completer asyncFunctionCompleter = Completer(); + + void callback(bool value) async { + asyncFunctionCompleter.complete(value); + } + + DDIEvent.instance.subscribeAsync(callback); + + DDIEvent.instance.fire(true); + + await expectLater(asyncFunctionCompleter.future, completion(isTrue)); + + DDIEvent.instance.unsubscribe(callback); + }); + + test('unsubscribeAsync', () async { + final Completer completer = Completer(); + + void callback(bool flag) { + completer.complete(flag); + } + + DDIEvent.instance.subscribeAsync(callback); + + DDIEvent.instance.unsubscribe(callback); + + expect(() => DDIEvent.instance.fire(true), + throwsA(isA())); + + await expectLater(completer.future, doesNotComplete); + }); + + test( + 'subscribeAsync with qualifier and fire event with qualifier asynchronously', + () async { + final Completer asyncFunctionCompleter = Completer(); + + void callback(bool value) { + asyncFunctionCompleter.complete(value); + } + + DDIEvent.instance + .subscribeAsync(callback, qualifier: 'test_qualifier'); + + DDIEvent.instance.fire(true, qualifier: 'test_qualifier'); + + await expectLater(asyncFunctionCompleter.future, completion(isTrue)); + + DDIEvent.instance + .unsubscribe(callback, qualifier: 'test_qualifier'); + }); + + test('subscribeAsync with registerIf and fire event based on registerIf', + () async { + final Completer asyncFunctionCompleter = Completer(); + + void callback(bool value) async { + asyncFunctionCompleter.complete(value); + } + + DDIEvent.instance.subscribeAsync( + callback, + registerIf: () => true, + ); + + DDIEvent.instance.fire(true); + + await expectLater(asyncFunctionCompleter.future, completion(isTrue)); + + DDIEvent.instance.unsubscribe(callback); + }); + + test('subscribeAsync and fire event with unsubscribeAfterFire', () async { + final Completer asyncFunctionCompleter = Completer(); + + void callback(bool value) async { + asyncFunctionCompleter.complete(value); + } + + DDIEvent.instance + .subscribeAsync(callback, unsubscribeAfterFire: true); + + DDIEvent.instance.fire(true); + + await expectLater(asyncFunctionCompleter.future, completion(isTrue)); + + await expectLater(() => DDIEvent.instance.fire(true), + throwsA(isA())); + }); + + test('subscribeAsync with multiple subscribers and fire event', () async { + final Completer asyncFunctionCompleter1 = Completer(); + final Completer asyncFunctionCompleter2 = Completer(); + + void callback1(bool value) async { + asyncFunctionCompleter1.complete(value); + } + + void callback2(bool value) async { + asyncFunctionCompleter2.complete(value); + } + + DDIEvent.instance.subscribeAsync(callback1); + DDIEvent.instance.subscribeAsync(callback2); + + DDIEvent.instance.fire(true); + + await expectLater(asyncFunctionCompleter1.future, completion(isTrue)); + await expectLater(asyncFunctionCompleter2.future, completion(isTrue)); + + DDIEvent.instance.unsubscribe(callback1); + DDIEvent.instance.unsubscribe(callback2); + }); }); } diff --git a/test/stream_test/stream_test.dart b/test/stream_test/stream_test.dart new file mode 100644 index 0000000..9c87ded --- /dev/null +++ b/test/stream_test/stream_test.dart @@ -0,0 +1,315 @@ +import 'dart:async'; + +import 'package:dart_ddi/dart_ddi.dart'; +import 'package:dart_ddi/src/exception/stream_not_found.dart'; +import 'package:test/test.dart'; + +void streamTest() { + group('DDIStream Tests', () { + late DDIStream ddiStream; + + setUp(() { + ddiStream = DDIStream.instance; + }); + + test('Subscribe and Fire', () async { + final completer = Completer(); + const testValue = 'TestValue'; + + void callback(String value) { + completer.complete(value); + } + + ddiStream.subscribe( + callback: callback, + ); + + ddiStream.fire( + value: testValue, + ); + + await expectLater(completer.future, completion(testValue)); + + ddiStream.close(); + }); + + test('Subscribe and Close', () async { + final completer = Completer(); + + void callback(String value) { + completer.complete(value); + } + + ddiStream.subscribe( + callback: callback, + ); + + ddiStream.close(); + + await expectLater(completer.future, doesNotComplete); + }); + + test('Unsubscribe After Fire', () async { + final completer = Completer(); + + void callback(String value) { + completer.complete(value); + } + + ddiStream.subscribe( + callback: callback, + unsubscribeAfterFire: true, + ); + + ddiStream.fire( + value: 'TestValue', + ); + + await expectLater(completer.future, completion('TestValue')); + + await expectLater( + () => ddiStream.fire( + value: 'AnotherValue', + ), + throwsA(isA())); + }); + test('Subscribe with Qualifier', () async { + final completer = Completer(); + const testValue = 'TestValue'; + const qualifier = 'TestQualifier'; + + void callback(String value) { + completer.complete(value); + } + + ddiStream.subscribe( + callback: callback, + qualifier: qualifier, + ); + + ddiStream.fire( + value: testValue, + qualifier: qualifier, + ); + + await expectLater(completer.future, completion(testValue)); + ddiStream.close(qualifier: qualifier); + }); + + test('Subscribe with RegisterIf', () async { + final completer = Completer(); + const testValue = 'TestValue'; + + void callback(String value) { + completer.complete(value); + } + + // Use registerIf to conditionally subscribe. + ddiStream.subscribe( + callback: callback, + registerIf: () => true, + ); + + ddiStream.fire( + value: testValue, + ); + + await expectLater(completer.future, completion(testValue)); + ddiStream.close(); + }); + + test('Subscribe with RegisterIf (Do Not Register)', () async { + final completer = Completer(); + + void callback(String value) { + completer.complete(value); + } + + // Use registerIf to conditionally subscribe, but return false. + ddiStream.subscribe( + callback: callback, + registerIf: () => false, + ); + + // Ensure the callback is not called as it was not registered. + await expectLater( + () => ddiStream.fire( + value: 'AnotherValue', + ), + throwsA(isA())); + await expectLater(completer.future, doesNotComplete); + }); + + test('Multiple Subscribers with Same Qualifier', () async { + final completer1 = Completer(); + final completer2 = Completer(); + const testValue = 'TestValue'; + const qualifier = 'TestQualifier'; + + void callback1(String value) { + completer1.complete(value); + } + + void callback2(String value) { + completer2.complete(value); + } + + ddiStream.subscribe( + callback: callback1, + qualifier: qualifier, + ); + + ddiStream.subscribe( + callback: callback2, + qualifier: qualifier, + ); + + ddiStream.fire( + value: testValue, + qualifier: qualifier, + ); + + await expectLater(completer1.future, completion(testValue)); + await expectLater(completer2.future, completion(testValue)); + ddiStream.close(qualifier: qualifier); + }); + + test('Subscribe and Unsubscribe', () async { + final completer = Completer(); + const testValue = 'TestValue'; + + void callback(String value) { + completer.complete(value); + } + + ddiStream.subscribe( + callback: callback, + ); + + ddiStream.fire( + value: testValue, + ); + + //The close methos doesn't cancel a event already fire + ddiStream.close(); + + await expectLater(completer.future, completion(testValue)); + }); + + test('Multiple Subscribers with Different Qualifiers', () async { + final completer1 = Completer(); + final completer2 = Completer(); + const testValue = 'TestValue'; + const qualifier1 = 'Qualifier1'; + const qualifier2 = 'Qualifier2'; + + void callback1(String value) { + completer1.complete(value); + } + + void callback2(String value) { + completer2.complete(value); + } + + ddiStream.subscribe( + callback: callback1, + qualifier: qualifier1, + ); + + ddiStream.subscribe( + callback: callback2, + qualifier: qualifier2, + ); + + ddiStream.fire( + value: testValue, + qualifier: qualifier1, + ); + + // Apenas completer1 deve ser concluĂ­do. + await expectLater(completer1.future, completion(testValue)); + await expectLater(completer2.future, doesNotComplete); + }); + + test('Subscribe and Fire with Multiple Values', () async { + final completer = Completer>(); + final testValues = ['TestValue1', 'TestValue2']; + final resultList = []; + + void callback(String value) { + resultList.add(value); + if (resultList.length == testValues.length) { + completer.complete(resultList); + } + } + + ddiStream.subscribe( + callback: callback, + ); + + for (final value in testValues) { + ddiStream.fire( + value: value, + ); + } + + // Garanta que todos os valores sejam recebidos no callback. + await expectLater(completer.future, completion(testValues)); + }); + + test('Close Specific Stream', () async { + final completer = Completer(); + + void callback(String value) { + completer.complete(value); + } + + ddiStream.subscribe( + callback: callback, + ); + + ddiStream.close(); + + await expectLater(completer.future, doesNotComplete); + }); + + test('Close Specific Stream with Qualifier', () async { + final completer = Completer(); + const qualifier = 'TestQualifier'; + + void callback(String value) { + completer.complete(value); + } + + ddiStream.subscribe( + callback: callback, + qualifier: qualifier, + ); + + ddiStream.close( + qualifier: qualifier, + ); + + await expectLater(completer.future, doesNotComplete); + }); + + test('Subscribe and Fire with Different Types', () async { + final completer = Completer(); + const testValue = 42; + + void callback(int value) { + completer.complete(value); + } + + ddiStream.subscribe( + callback: callback, + ); + + ddiStream.fire( + value: testValue, + ); + + await expectLater(completer.future, completion(testValue)); + }); + }); +}