Add: adding forked event_bus_plus as project event bus
This commit is contained in:
parent
c194fadbc2
commit
11c12fd590
28 changed files with 1445 additions and 0 deletions
54
core/eventbus/.github/workflows/dart.yml
vendored
Normal file
54
core/eventbus/.github/workflows/dart.yml
vendored
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
# This workflow uses actions that are not certified by GitHub.
|
||||||
|
# They are provided by a third-party and are governed by
|
||||||
|
# separate terms of service, privacy policy, and support
|
||||||
|
# documentation.
|
||||||
|
|
||||||
|
name: Dart
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ main ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
# Note: This workflow uses the latest stable version of the Dart SDK.
|
||||||
|
# You can specify other versions if desired, see documentation here:
|
||||||
|
# https://github.com/dart-lang/setup-dart/blob/main/README.md
|
||||||
|
# - uses: dart-lang/setup-dart@v1
|
||||||
|
- uses: dart-lang/setup-dart@9a04e6d73cca37bd455e0608d7e5092f881fd603
|
||||||
|
|
||||||
|
- name: Flutter action
|
||||||
|
# You may pin to the exact commit or the version.
|
||||||
|
# uses: subosito/flutter-action@4389e6cbc6cb8a4b18c628ff96ff90be0e926aa8
|
||||||
|
uses: subosito/flutter-action@v1.5.3
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: dart pub get
|
||||||
|
|
||||||
|
# Uncomment this step to verify the use of 'dart format' on each commit.
|
||||||
|
# - name: Verify formatting
|
||||||
|
# run: dart format --output=none --set-exit-if-changed .
|
||||||
|
|
||||||
|
# Consider passing '--fatal-infos' for slightly stricter analysis.
|
||||||
|
- name: Analyze project source
|
||||||
|
run: dart analyze
|
||||||
|
|
||||||
|
# Your project will need to have tests in test/ and a dependency on
|
||||||
|
# package:test for this step to succeed. Note that Flutter projects will
|
||||||
|
# want to change this to 'flutter test'.
|
||||||
|
- name: Run tests
|
||||||
|
run: flutter test --coverage
|
||||||
|
|
||||||
|
- name: Install lcov
|
||||||
|
run: sudo apt-get install -y lcov
|
||||||
|
- name: Upload coverage to Codecov
|
||||||
|
uses: codecov/codecov-action@v2
|
||||||
|
with:
|
||||||
|
file: coverage/lcov.info
|
30
core/eventbus/.gitignore
vendored
Normal file
30
core/eventbus/.gitignore
vendored
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# Miscellaneous
|
||||||
|
*.class
|
||||||
|
*.log
|
||||||
|
*.pyc
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
.atom/
|
||||||
|
.buildlog/
|
||||||
|
.history
|
||||||
|
.svn/
|
||||||
|
|
||||||
|
# IntelliJ related
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# The .vscode folder contains launch configuration and tasks you configure in
|
||||||
|
# VS Code which you may wish to be included in version control, so this line
|
||||||
|
# is commented out by default.
|
||||||
|
#.vscode/
|
||||||
|
|
||||||
|
# Flutter/Dart/Pub related
|
||||||
|
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||||
|
/pubspec.lock
|
||||||
|
**/doc/api/
|
||||||
|
.dart_tool/
|
||||||
|
.packages
|
||||||
|
build/
|
||||||
|
coverage
|
10
core/eventbus/.metadata
Normal file
10
core/eventbus/.metadata
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
# This file tracks properties of this Flutter project.
|
||||||
|
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||||
|
#
|
||||||
|
# This file should be version controlled and should not be manually edited.
|
||||||
|
|
||||||
|
version:
|
||||||
|
revision: 7e9793dee1b85a243edd0e06cb1658e98b077561
|
||||||
|
channel: stable
|
||||||
|
|
||||||
|
project_type: package
|
67
core/eventbus/CHANGELOG.md
Normal file
67
core/eventbus/CHANGELOG.md
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
## 0.6.2
|
||||||
|
|
||||||
|
* update `logger` dependency
|
||||||
|
|
||||||
|
## 0.6.1
|
||||||
|
|
||||||
|
* [Add] `allowLogging` to control logging (thanks [@hatemragab](https://github.com/hatemragab))
|
||||||
|
|
||||||
|
## 0.6.0
|
||||||
|
|
||||||
|
* **[Change]** fire `EmptyEvent` after each event to keep stream empty.
|
||||||
|
* [Add] [Logger](https://pub.dev/packages/logger)
|
||||||
|
|
||||||
|
## 0.5.0
|
||||||
|
|
||||||
|
* [Add] timestamp for each `event`.
|
||||||
|
|
||||||
|
## 0.4.0
|
||||||
|
|
||||||
|
* [Add] mapper
|
||||||
|
|
||||||
|
## 0.3.0+3
|
||||||
|
|
||||||
|
* Downgrade clock version
|
||||||
|
|
||||||
|
## 0.3.0+2
|
||||||
|
|
||||||
|
* Improve documentation
|
||||||
|
|
||||||
|
## 0.3.0+1
|
||||||
|
|
||||||
|
* Improve documentation
|
||||||
|
|
||||||
|
## 0.3.0
|
||||||
|
|
||||||
|
* [Add] `EventCompletionEvent`
|
||||||
|
* Improve documentation
|
||||||
|
|
||||||
|
## 0.2.2
|
||||||
|
|
||||||
|
* [Fix] `clock` dependency
|
||||||
|
|
||||||
|
## 0.2.1
|
||||||
|
|
||||||
|
* [Add] debug logging
|
||||||
|
|
||||||
|
## 0.2.0
|
||||||
|
|
||||||
|
* [Add] history
|
||||||
|
|
||||||
|
## 0.1.0
|
||||||
|
|
||||||
|
* [Add] distinct
|
||||||
|
|
||||||
|
## 0.0.2+1
|
||||||
|
|
||||||
|
* [Fix] GitHub repository link
|
||||||
|
|
||||||
|
## 0.0.2
|
||||||
|
|
||||||
|
* [Remove] `id` of `AppEvent`
|
||||||
|
|
||||||
|
## 0.0.1
|
||||||
|
|
||||||
|
* Initial release.
|
||||||
|
* [Add] `EventBus`
|
||||||
|
* [Add] `AppEvent` superclass
|
21
core/eventbus/LICENSE
Normal file
21
core/eventbus/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2021 Andrew
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
98
core/eventbus/README.md
Normal file
98
core/eventbus/README.md
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
# EventBus: Events for Dart/Flutter
|
||||||
|
|
||||||
|
[![pub package](https://img.shields.io/pub/v/event_bus_plus.svg?label=event_bus_plus&color=blue)](https://pub.dev/packages/event_bus_plus)
|
||||||
|
[![codecov](https://codecov.io/gh/AndrewPiterov/event_bus_plus/branch/main/graph/badge.svg?token=VM9LTJXGQS)](https://codecov.io/gh/AndrewPiterov/event_bus_plus)
|
||||||
|
[![Dart](https://github.com/AndrewPiterov/event_bus_plus/actions/workflows/dart.yml/badge.svg)](https://github.com/AndrewPiterov/event_bus_plus/actions/workflows/dart.yml)
|
||||||
|
|
||||||
|
**EventBus** is an open-source library for Dart and Flutter using the **publisher/subscriber** pattern for loose coupling. **EventBus** enables central communication to decoupled classes with just a few lines of code – simplifying the code, removing dependencies, and speeding up app development.
|
||||||
|
|
||||||
|
<img src="https://raw.githubusercontent.com/andrewpiterov/event_bus_plus/main/doc/pub_sub.webp" alt="event bus publish subscribe" style="width: 100%; height: auto; "/>
|
||||||
|
|
||||||
|
## Your benefits using EventBus: It…
|
||||||
|
- simplifies the communication between components;
|
||||||
|
- decouples event senders and receivers;
|
||||||
|
- performs well with UI artifacts (e.g. Widgets, Controllers);
|
||||||
|
- avoids complex and error-prone dependencies and life cycle issues.
|
||||||
|
|
||||||
|
|
||||||
|
<img src="https://raw.githubusercontent.com/andrewpiterov/event_bus_plus/main/doc/video_presentation.gif" alt="event bus plus" style="width: 100%; height: auto; "/>
|
||||||
|
|
||||||
|
### Define the app's events
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// Initialize the Service Bus
|
||||||
|
IAppEventBus eventBus = AppEventBus();
|
||||||
|
|
||||||
|
// Define your app events
|
||||||
|
final event = FollowEvent('@devcraft.ninja');
|
||||||
|
final event = CommentEvent('Awesome package 😎');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Subscribe
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// listen the latest event
|
||||||
|
final sub = eventBus.last$
|
||||||
|
.listen((AppEvent event) { /*do something*/ });
|
||||||
|
|
||||||
|
// Listen particular event
|
||||||
|
final sub2 = eventBus.on<FollowAppEvent>()
|
||||||
|
.listen((e) { /*do something*/ });
|
||||||
|
```
|
||||||
|
|
||||||
|
### Publish
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// fire the event
|
||||||
|
eventBus.fire(event);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Watch events in progress
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// start watch the event till its completion
|
||||||
|
eventBus.watch(event);
|
||||||
|
|
||||||
|
// and check the progress
|
||||||
|
eventBus.isInProgress<FollowAppEvent>();
|
||||||
|
|
||||||
|
// or listen stream to check the processing
|
||||||
|
eventBus.inProgress$.map((List<AppEvent> events) =>
|
||||||
|
events.whereType<FollowAppEvent>().isNotEmpty);
|
||||||
|
|
||||||
|
// complete
|
||||||
|
_eventBus.complete(event);
|
||||||
|
|
||||||
|
// or complete with completion event
|
||||||
|
_eventBus.complete(event, nextEvent: SomeAnotherEvent);
|
||||||
|
```
|
||||||
|
|
||||||
|
## History
|
||||||
|
|
||||||
|
```dart
|
||||||
|
final events = eventBus.history;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Mapping
|
||||||
|
|
||||||
|
```dart
|
||||||
|
final eventBus = bus = EventBus(
|
||||||
|
map: {
|
||||||
|
SomeEvent: [
|
||||||
|
(e) => SomeAnotherEvent(),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
We accept the following contributions:
|
||||||
|
|
||||||
|
* Improving the documentation
|
||||||
|
* [Reporting issues](https://github.com/AndrewPiterov/event_bus_plus/issues/new)
|
||||||
|
* Fixing bugs
|
||||||
|
|
||||||
|
## Maintainers
|
||||||
|
|
||||||
|
* [Andrew Piterov](mailto:contact@andrewpiterov.com?subject=[GitHub]%20Source%20Dart%event_bus_plus)
|
224
core/eventbus/analysis_options.yaml
Normal file
224
core/eventbus/analysis_options.yaml
Normal file
|
@ -0,0 +1,224 @@
|
||||||
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
analyzer:
|
||||||
|
strong-mode:
|
||||||
|
implicit-casts: false
|
||||||
|
implicit-dynamic: true
|
||||||
|
errors:
|
||||||
|
# Close instances of dart.core.Sink.
|
||||||
|
close_sinks: error
|
||||||
|
# Cancel instances of dart.async.StreamSubscription.
|
||||||
|
cancel_subscriptions: error
|
||||||
|
# treat missing required parameters as a error (not a hint)
|
||||||
|
missing_required_param: error
|
||||||
|
# treat missing returns as a error (not a hint)
|
||||||
|
missing_return: error
|
||||||
|
# allow having TODOs in the code and treat as warning
|
||||||
|
todo: warning
|
||||||
|
# allow self-reference to deprecated members (we do this because otherwise we have
|
||||||
|
# to annotate every member in every test, assert, etc, when we deprecate something)
|
||||||
|
deprecated_member_use_from_same_package: ignore
|
||||||
|
# Ignore analyzer hints for updating pubspecs when using Future or
|
||||||
|
# Stream and not importing dart:async
|
||||||
|
# Please see https://github.com/flutter/flutter/pull/24528 for details.
|
||||||
|
sdk_version_async_exported_from_core: ignore
|
||||||
|
# await
|
||||||
|
unawaited_futures: error
|
||||||
|
avoid_print: warning
|
||||||
|
always_declare_return_types: error
|
||||||
|
unrelated_type_equality_checks: error
|
||||||
|
implementation_imports: error
|
||||||
|
require_trailing_commas: warning
|
||||||
|
avoid_slow_async_io: error
|
||||||
|
use_build_context_synchronously: error
|
||||||
|
sized_box_for_whitespace: error
|
||||||
|
dead_code: warning
|
||||||
|
invalid_assignment: error
|
||||||
|
|
||||||
|
exclude:
|
||||||
|
- "bin/cache/**"
|
||||||
|
# the following two are relative to the stocks example and the flutter package respectively
|
||||||
|
# see https://github.com/dart-lang/sdk/issues/28463
|
||||||
|
- "lib/i18n/messages_*.dart"
|
||||||
|
- "lib/src/http/**"
|
||||||
|
- "build/**"
|
||||||
|
- "lib/**.freezed.dart"
|
||||||
|
- "lib/**.g.dart"
|
||||||
|
- "test/**"
|
||||||
|
- "example/**"
|
||||||
|
|
||||||
|
linter:
|
||||||
|
rules:
|
||||||
|
# these rules are documented on and in the same order as
|
||||||
|
# the Dart Lint rules page to make maintenance easier
|
||||||
|
# https://github.com/dart-lang/linter/blob/master/example/all.yaml
|
||||||
|
- always_declare_return_types
|
||||||
|
- always_put_control_body_on_new_line
|
||||||
|
# - always_put_required_named_parameters_first # we prefer having parameters in the same order as fields https://github.com/flutter/flutter/issues/10219
|
||||||
|
- always_require_non_null_named_parameters
|
||||||
|
# - always_specify_types
|
||||||
|
- annotate_overrides
|
||||||
|
# - avoid_annotating_with_dynamic # conflicts with always_specify_types
|
||||||
|
# - avoid_as # required for implicit-casts: true
|
||||||
|
- avoid_bool_literals_in_conditional_expressions
|
||||||
|
# - avoid_catches_without_on_clauses # we do this commonly
|
||||||
|
# - avoid_catching_errors # we do this commonly
|
||||||
|
- avoid_classes_with_only_static_members
|
||||||
|
# - avoid_double_and_int_checks # only useful when targeting JS runtime
|
||||||
|
- avoid_empty_else
|
||||||
|
- avoid_equals_and_hash_code_on_mutable_classes
|
||||||
|
- avoid_field_initializers_in_const_classes
|
||||||
|
- avoid_function_literals_in_foreach_calls
|
||||||
|
# - avoid_implementing_value_types # not yet tested
|
||||||
|
- avoid_init_to_null
|
||||||
|
# - avoid_js_rounded_ints # only useful when targeting JS runtime
|
||||||
|
- avoid_null_checks_in_equality_operators
|
||||||
|
# - avoid_positional_boolean_parameters # not yet tested
|
||||||
|
- avoid_print # not yet tested
|
||||||
|
# - avoid_private_typedef_functions # we prefer having typedef (discussion in https://github.com/flutter/flutter/pull/16356)
|
||||||
|
# - avoid_redundant_argument_values # not yet tested
|
||||||
|
- avoid_relative_lib_imports
|
||||||
|
- avoid_renaming_method_parameters
|
||||||
|
- avoid_return_types_on_setters
|
||||||
|
# - avoid_returning_null # there are plenty of valid reasons to return null
|
||||||
|
# - avoid_returning_null_for_future # not yet tested
|
||||||
|
- avoid_returning_null_for_void
|
||||||
|
# - avoid_returning_this # there are plenty of valid reasons to return this
|
||||||
|
# - avoid_setters_without_getters # not yet tested
|
||||||
|
- avoid_shadowing_type_parameters # not yet tested
|
||||||
|
- avoid_single_cascade_in_expression_statements
|
||||||
|
- avoid_slow_async_io
|
||||||
|
- avoid_types_as_parameter_names
|
||||||
|
# - avoid_types_on_closure_parameters # conflicts with always_specify_types
|
||||||
|
# - avoid_unnecessary_containers # not yet tested
|
||||||
|
- avoid_unused_constructor_parameters
|
||||||
|
- avoid_void_async
|
||||||
|
# - avoid_web_libraries_in_flutter # not yet tested
|
||||||
|
- await_only_futures
|
||||||
|
- camel_case_extensions
|
||||||
|
- camel_case_types
|
||||||
|
- cancel_subscriptions
|
||||||
|
# - cascade_invocations # not yet tested
|
||||||
|
# - close_sinks # not reliable enough
|
||||||
|
# - comment_references # blocked on https://github.com/flutter/flutter/issues/20765
|
||||||
|
# - constant_identifier_names # needs an opt-out https://github.com/dart-lang/linter/issues/204
|
||||||
|
- control_flow_in_finally
|
||||||
|
# - curly_braces_in_flow_control_structures # not yet tested
|
||||||
|
# - diagnostic_describe_all_properties # not yet tested
|
||||||
|
- directives_ordering
|
||||||
|
- empty_catches
|
||||||
|
- empty_constructor_bodies
|
||||||
|
- empty_statements
|
||||||
|
# - file_names # not yet tested
|
||||||
|
- flutter_style_todos
|
||||||
|
- hash_and_equals
|
||||||
|
- implementation_imports
|
||||||
|
# - invariant_booleans # too many false positives: https://github.com/dart-lang/linter/issues/811
|
||||||
|
- iterable_contains_unrelated_type
|
||||||
|
# - join_return_with_assignment # not yet tested
|
||||||
|
- library_names
|
||||||
|
- library_prefixes
|
||||||
|
# - lines_longer_than_80_chars # not yet tested
|
||||||
|
- list_remove_unrelated_type
|
||||||
|
# - literal_only_boolean_expressions # too many false positives: https://github.com/dart-lang/sdk/issues/34181
|
||||||
|
# - missing_whitespace_between_adjacent_strings # not yet tested
|
||||||
|
- no_adjacent_strings_in_list
|
||||||
|
- no_duplicate_case_values
|
||||||
|
# - no_logic_in_create_state # not yet tested
|
||||||
|
# - no_runtimeType_toString # not yet tested
|
||||||
|
- non_constant_identifier_names
|
||||||
|
# - null_closures # not yet tested
|
||||||
|
- omit_local_variable_types # opposite of always_specify_types
|
||||||
|
# - one_member_abstracts # too many false positives
|
||||||
|
# - only_throw_errors # https://github.com/flutter/flutter/issues/5792
|
||||||
|
- overridden_fields
|
||||||
|
- package_api_docs
|
||||||
|
- package_names
|
||||||
|
- package_prefixed_library_names
|
||||||
|
# - parameter_assignments # we do this commonly
|
||||||
|
- prefer_adjacent_string_concatenation
|
||||||
|
- prefer_asserts_in_initializer_lists
|
||||||
|
# - prefer_asserts_with_message # not yet tested
|
||||||
|
- prefer_collection_literals
|
||||||
|
- prefer_conditional_assignment
|
||||||
|
- prefer_const_constructors
|
||||||
|
- prefer_const_constructors_in_immutables
|
||||||
|
- prefer_const_declarations
|
||||||
|
- prefer_const_literals_to_create_immutables
|
||||||
|
# - prefer_constructors_over_static_methods # not yet tested
|
||||||
|
- prefer_contains
|
||||||
|
# - prefer_double_quotes # opposite of prefer_single_quotes
|
||||||
|
- prefer_equal_for_default_values
|
||||||
|
# - prefer_expression_function_bodies # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#consider-using--for-short-functions-and-methods
|
||||||
|
- prefer_final_fields
|
||||||
|
- prefer_final_in_for_each
|
||||||
|
- prefer_final_locals
|
||||||
|
- prefer_for_elements_to_map_fromIterable
|
||||||
|
- prefer_foreach
|
||||||
|
# - prefer_function_declarations_over_variables # not yet tested
|
||||||
|
- prefer_generic_function_type_aliases
|
||||||
|
- prefer_if_elements_to_conditional_expressions
|
||||||
|
- prefer_if_null_operators
|
||||||
|
- prefer_initializing_formals
|
||||||
|
- prefer_inlined_adds
|
||||||
|
# - prefer_int_literals # not yet tested
|
||||||
|
# - prefer_interpolation_to_compose_strings # not yet tested
|
||||||
|
- prefer_is_empty
|
||||||
|
- prefer_is_not_empty
|
||||||
|
- prefer_is_not_operator
|
||||||
|
- prefer_iterable_whereType
|
||||||
|
# - prefer_mixin # https://github.com/dart-lang/language/issues/32
|
||||||
|
# - prefer_null_aware_operators # disable until NNBD, see https://github.com/flutter/flutter/pull/32711#issuecomment-492930932
|
||||||
|
# - prefer_relative_imports # not yet tested
|
||||||
|
- prefer_single_quotes
|
||||||
|
- prefer_spread_collections
|
||||||
|
- prefer_typing_uninitialized_variables
|
||||||
|
- prefer_void_to_null
|
||||||
|
# - provide_deprecation_message # not yet tested
|
||||||
|
- public_member_api_docs # enabled on a case-by-case basis; see e.g. packages/analysis_options.yaml
|
||||||
|
- recursive_getters
|
||||||
|
- slash_for_doc_comments
|
||||||
|
# - sort_child_properties_last # not yet tested
|
||||||
|
- sort_constructors_first
|
||||||
|
- sort_pub_dependencies
|
||||||
|
- sort_unnamed_constructors_first
|
||||||
|
- test_types_in_equals
|
||||||
|
- throw_in_finally
|
||||||
|
# - type_annotate_public_apis # subset of always_specify_types
|
||||||
|
- type_init_formals
|
||||||
|
- unawaited_futures # too many false positives
|
||||||
|
- unnecessary_await_in_return # not yet tested
|
||||||
|
- unnecessary_brace_in_string_interps
|
||||||
|
- unnecessary_const
|
||||||
|
# - unnecessary_final # conflicts with prefer_final_locals
|
||||||
|
- unnecessary_getters_setters
|
||||||
|
# - unnecessary_lambdas # has false positives: https://github.com/dart-lang/linter/issues/498
|
||||||
|
- unnecessary_new
|
||||||
|
- unnecessary_null_aware_assignments
|
||||||
|
- unnecessary_null_in_if_null_operators
|
||||||
|
- unnecessary_overrides
|
||||||
|
- unnecessary_parenthesis
|
||||||
|
- unnecessary_statements
|
||||||
|
- unnecessary_string_interpolations
|
||||||
|
- unnecessary_this
|
||||||
|
- unrelated_type_equality_checks
|
||||||
|
# - unsafe_html # not yet tested
|
||||||
|
- use_full_hex_values_for_flutter_colors
|
||||||
|
# - use_function_type_syntax_for_parameters # not yet tested
|
||||||
|
# - use_key_in_widget_constructors # not yet tested
|
||||||
|
- use_late_for_private_fields_and_variables
|
||||||
|
- use_rethrow_when_possible
|
||||||
|
# - use_setters_to_change_properties # not yet tested
|
||||||
|
# - use_string_buffers # has false positives: https://github.com/dart-lang/sdk/issues/34182
|
||||||
|
# - use_to_and_as_if_applicable # has false positives, so we prefer to catch this by code-review
|
||||||
|
- valid_regexps
|
||||||
|
- void_checks
|
||||||
|
|
||||||
|
dart_code_metrics:
|
||||||
|
rules:
|
||||||
|
- newline-before-return:
|
||||||
|
severity: style
|
||||||
|
- no-boolean-literal-compare:
|
||||||
|
severity: error
|
||||||
|
anti-patterns:
|
||||||
|
- long-method:
|
||||||
|
severity: warning
|
BIN
core/eventbus/doc/pub_sub.webp
Normal file
BIN
core/eventbus/doc/pub_sub.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
BIN
core/eventbus/doc/video_presentation.gif
Normal file
BIN
core/eventbus/doc/video_presentation.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 MiB |
3
core/eventbus/lib/event_bus_plus.dart
Normal file
3
core/eventbus/lib/event_bus_plus.dart
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
library event_bus_plus;
|
||||||
|
|
||||||
|
export 'res/res.dart';
|
29
core/eventbus/lib/res/app_event.dart
Normal file
29
core/eventbus/lib/res/app_event.dart
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
|
/// The base class for all events
|
||||||
|
abstract class AppEvent extends Equatable {
|
||||||
|
/// Create the event
|
||||||
|
const AppEvent();
|
||||||
|
|
||||||
|
/// The event time
|
||||||
|
DateTime get timestamp => clock.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The event completion event
|
||||||
|
class EventCompletionEvent extends AppEvent {
|
||||||
|
/// Create the event
|
||||||
|
const EventCompletionEvent(this.event);
|
||||||
|
|
||||||
|
/// The event that is completed
|
||||||
|
final AppEvent event;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [event];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The empty event
|
||||||
|
class EmptyEvent extends AppEvent {
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [];
|
||||||
|
}
|
212
core/eventbus/lib/res/event_bus.dart
Normal file
212
core/eventbus/lib/res/event_bus.dart
Normal file
|
@ -0,0 +1,212 @@
|
||||||
|
import 'package:logger/logger.dart';
|
||||||
|
import 'package:angel3_reactivex/subjects.dart';
|
||||||
|
|
||||||
|
import 'app_event.dart';
|
||||||
|
import 'history_entry.dart';
|
||||||
|
import 'subscription.dart';
|
||||||
|
|
||||||
|
/// The event bus interface
|
||||||
|
abstract class IEventBus {
|
||||||
|
/// Whether the event bus is busy
|
||||||
|
bool get isBusy;
|
||||||
|
|
||||||
|
/// Whether the event bus is busy
|
||||||
|
Stream<bool> get isBusy$;
|
||||||
|
|
||||||
|
/// The last event
|
||||||
|
AppEvent? get last;
|
||||||
|
|
||||||
|
/// The last event
|
||||||
|
Stream<AppEvent?> get last$;
|
||||||
|
|
||||||
|
/// The list of events that are in progress
|
||||||
|
Stream<List<AppEvent>> get inProgress$;
|
||||||
|
|
||||||
|
/// Subscribe `EventBus` on a specific type of event, and register responder to it.
|
||||||
|
Stream<T> on<T extends AppEvent>();
|
||||||
|
|
||||||
|
/// Subscribe `EventBus` on a specific type of event, and register responder to it.
|
||||||
|
Stream<bool> whileInProgress<T extends AppEvent>();
|
||||||
|
|
||||||
|
/// Subscribe `EventBus` on a specific type of event, and register responder to it.
|
||||||
|
Subscription respond<T>(Responder<T> responder);
|
||||||
|
|
||||||
|
/// The history of events
|
||||||
|
List<EventBusHistoryEntry> get history;
|
||||||
|
|
||||||
|
/// Fire a event
|
||||||
|
void fire(AppEvent event);
|
||||||
|
|
||||||
|
/// Fire a event and wait for it to be completed
|
||||||
|
void watch(AppEvent event);
|
||||||
|
|
||||||
|
/// Complete a event
|
||||||
|
void complete(AppEvent event, {AppEvent? nextEvent});
|
||||||
|
|
||||||
|
///
|
||||||
|
bool isInProgress<T>();
|
||||||
|
|
||||||
|
/// Reset the event bus
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
/// Dispose the event bus
|
||||||
|
void dispose();
|
||||||
|
|
||||||
|
/// Clear the history
|
||||||
|
void clearHistory();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The event bus implementation
|
||||||
|
class EventBus implements IEventBus {
|
||||||
|
/// Create the event bus
|
||||||
|
EventBus({
|
||||||
|
this.maxHistoryLength = 100,
|
||||||
|
this.map = const {},
|
||||||
|
this.allowLogging = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
final _logger = Logger();
|
||||||
|
|
||||||
|
/// The maximum length of history
|
||||||
|
final int maxHistoryLength;
|
||||||
|
|
||||||
|
/// allow to log all events this when you call [fire]
|
||||||
|
/// the event will be in console log
|
||||||
|
final bool allowLogging;
|
||||||
|
|
||||||
|
/// The map of events
|
||||||
|
final Map<Type, List<AppEvent Function(AppEvent event)>> map;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isBusy => _inProgress.value.isNotEmpty;
|
||||||
|
@override
|
||||||
|
Stream<bool> get isBusy$ => _inProgress.map((event) => event.isNotEmpty);
|
||||||
|
|
||||||
|
final _lastEventSubject = BehaviorSubject<AppEvent>();
|
||||||
|
@override
|
||||||
|
AppEvent? get last => _lastEventSubject.valueOrNull;
|
||||||
|
@override
|
||||||
|
Stream<AppEvent?> get last$ => _lastEventSubject.distinct();
|
||||||
|
|
||||||
|
final _inProgress = BehaviorSubject<List<AppEvent>>.seeded([]);
|
||||||
|
List<AppEvent> get _isInProgressEvents => _inProgress.value;
|
||||||
|
@override
|
||||||
|
Stream<List<AppEvent>> get inProgress$ => _inProgress;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<EventBusHistoryEntry> get history => List.unmodifiable(_history);
|
||||||
|
final List<EventBusHistoryEntry> _history = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void fire(AppEvent event) {
|
||||||
|
if (_history.length >= maxHistoryLength) {
|
||||||
|
_history.removeAt(0);
|
||||||
|
}
|
||||||
|
_history.add(EventBusHistoryEntry(event, event.timestamp));
|
||||||
|
// 1. Fire the event
|
||||||
|
_lastEventSubject.add(event);
|
||||||
|
// 2. Map if needed
|
||||||
|
_map(event);
|
||||||
|
// 3. Reset stream
|
||||||
|
_lastEventSubject.add(EmptyEvent());
|
||||||
|
if (allowLogging) {
|
||||||
|
_logger.d(' ⚡️ [${event.timestamp}] $event');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void watch(AppEvent event) {
|
||||||
|
fire(event);
|
||||||
|
_inProgress.add([
|
||||||
|
..._isInProgressEvents,
|
||||||
|
event,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void complete(AppEvent event, {AppEvent? nextEvent}) {
|
||||||
|
// complete the event
|
||||||
|
if (_isInProgressEvents.any((e) => e == event)) {
|
||||||
|
final newArr = _isInProgressEvents.toList()
|
||||||
|
..removeWhere((e) => e == event);
|
||||||
|
_inProgress.add(newArr);
|
||||||
|
fire(EventCompletionEvent(event));
|
||||||
|
}
|
||||||
|
|
||||||
|
// fire next event if any
|
||||||
|
if (nextEvent != null) {
|
||||||
|
fire(nextEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool isInProgress<T>() {
|
||||||
|
return _isInProgressEvents.whereType<T>().isNotEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<T> on<T extends AppEvent>() {
|
||||||
|
if (T == dynamic) {
|
||||||
|
return _lastEventSubject.stream as Stream<T>;
|
||||||
|
} else {
|
||||||
|
return _lastEventSubject.stream.where((event) => event is T).cast<T>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subscribe `EventBus` on a specific type of event, and register responder to it.
|
||||||
|
///
|
||||||
|
/// When [T] is not given or given as `dynamic`, it listens to all events regardless of the type.
|
||||||
|
/// Returns [Subscription], which can be disposed to cancel all the subscription registered to itself.
|
||||||
|
@override
|
||||||
|
Subscription respond<T>(Responder<T> responder) =>
|
||||||
|
Subscription(_lastEventSubject).respond<T>(responder);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<bool> whileInProgress<T extends AppEvent>() {
|
||||||
|
return _inProgress.map((events) {
|
||||||
|
return events.whereType<T>().isNotEmpty;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _map(AppEvent? event) {
|
||||||
|
if (event == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final functions = map[event.runtimeType] ?? [];
|
||||||
|
if (functions.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final func in functions) {
|
||||||
|
final newEvent = func(event);
|
||||||
|
if (newEvent.runtimeType == event.runtimeType) {
|
||||||
|
if (allowLogging) {
|
||||||
|
_logger.d(
|
||||||
|
' 🟠 SKIP EVENT: ${newEvent.runtimeType} => ${event.runtimeType}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
fire(newEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void clearHistory() {
|
||||||
|
_history.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void reset() {
|
||||||
|
clearHistory();
|
||||||
|
_inProgress.add([]);
|
||||||
|
_lastEventSubject.add(EmptyEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_inProgress.close();
|
||||||
|
_lastEventSubject.close();
|
||||||
|
}
|
||||||
|
}
|
18
core/eventbus/lib/res/history_entry.dart
Normal file
18
core/eventbus/lib/res/history_entry.dart
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
|
import 'app_event.dart';
|
||||||
|
|
||||||
|
/// The history entry
|
||||||
|
class EventBusHistoryEntry extends Equatable {
|
||||||
|
/// The history entry
|
||||||
|
const EventBusHistoryEntry(this.event, this.timestamp);
|
||||||
|
|
||||||
|
/// The event
|
||||||
|
final AppEvent event;
|
||||||
|
|
||||||
|
/// The timestamp
|
||||||
|
final DateTime timestamp;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [event, timestamp];
|
||||||
|
}
|
4
core/eventbus/lib/res/res.dart
Normal file
4
core/eventbus/lib/res/res.dart
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
export 'app_event.dart';
|
||||||
|
export 'event_bus.dart';
|
||||||
|
export 'history_entry.dart';
|
||||||
|
export 'subscription.dart';
|
78
core/eventbus/lib/res/subscription.dart
Normal file
78
core/eventbus/lib/res/subscription.dart
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
/// The function/method signature for the event handler
|
||||||
|
typedef Responder<T> = void Function(T event);
|
||||||
|
|
||||||
|
/// The class manages the subscription to event bus
|
||||||
|
class Subscription {
|
||||||
|
/// Create the subscription
|
||||||
|
///
|
||||||
|
/// Should barely used directly, to subscribe to event bus, use `EventBus.respond`.
|
||||||
|
Subscription(this._stream);
|
||||||
|
|
||||||
|
/// Returns an instance that indicates there is no subscription
|
||||||
|
factory Subscription.empty() => const _EmptySubscription();
|
||||||
|
|
||||||
|
final Stream _stream;
|
||||||
|
|
||||||
|
/// Subscriptions that registered to event bus
|
||||||
|
final List<StreamSubscription> subscriptions = [];
|
||||||
|
|
||||||
|
Stream<T> _cast<T>() {
|
||||||
|
if (T == dynamic) {
|
||||||
|
return _stream as Stream<T>;
|
||||||
|
} else {
|
||||||
|
return _stream.where((event) => event is T).cast<T>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register a [responder] to event bus for the event type [T].
|
||||||
|
/// If [T] is omitted or given as `dynamic`, it listens to all events that published on [EventBus].
|
||||||
|
///
|
||||||
|
/// Method call can be safely chained, and the order doesn't matter.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// eventBus
|
||||||
|
/// .respond<EventA>(responderA)
|
||||||
|
/// .respond<EventB>(responderB);
|
||||||
|
/// ```
|
||||||
|
Subscription respond<T>(Responder<T> responder) {
|
||||||
|
subscriptions.add(_cast<T>().listen(responder));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cancel all the registered subscriptions.
|
||||||
|
/// After calling this method, all the events published won't be delivered to the cleared responders any more.
|
||||||
|
///
|
||||||
|
/// No harm to call more than once.
|
||||||
|
void dispose() {
|
||||||
|
if (subscriptions.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (final s in subscriptions) {
|
||||||
|
s.cancel();
|
||||||
|
}
|
||||||
|
subscriptions.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _EmptySubscription implements Subscription {
|
||||||
|
const _EmptySubscription();
|
||||||
|
static final List<StreamSubscription> emptyList =
|
||||||
|
List.unmodifiable(<StreamSubscription>[]);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Subscription respond<T>(responder) => throw Exception('Not supported');
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<StreamSubscription> get subscriptions => emptyList;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<T> _cast<T>() => throw Exception('Not supported');
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream get _stream => throw Exception('Not supported');
|
||||||
|
}
|
20
core/eventbus/pubspec.yaml
Normal file
20
core/eventbus/pubspec.yaml
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
name: angel3_event_bus
|
||||||
|
description: Event Bus for Dart.
|
||||||
|
version: 0.6.2
|
||||||
|
homepage: https://github.com/AndrewPiterov/event_bus_plus
|
||||||
|
|
||||||
|
environment:
|
||||||
|
sdk: '>=2.17.1 <4.0.0'
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
clock: ^1.1.0
|
||||||
|
equatable: ^2.0.5
|
||||||
|
logger: ^2.0.2+1
|
||||||
|
angel3_reactivex: ^0.27.5
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
flutter_lints: ^3.0.0
|
||||||
|
given_when_then_unit_test: ^0.2.1
|
||||||
|
shouldly: ^0.5.0+1
|
||||||
|
test: ^1.22.0
|
||||||
|
|
70
core/eventbus/test/completion_test.dart
Normal file
70
core/eventbus/test/completion_test.dart
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
import 'package:event_bus_plus/event_bus_plus.dart';
|
||||||
|
import 'package:given_when_then_unit_test/given_when_then_unit_test.dart';
|
||||||
|
import 'models.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late IEventBus bus;
|
||||||
|
|
||||||
|
before(() {
|
||||||
|
bus = EventBus();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('emit Follower Events', () {
|
||||||
|
expect(
|
||||||
|
bus.on(),
|
||||||
|
emitsInOrder([
|
||||||
|
const FollowAppEvent('username'),
|
||||||
|
EmptyEvent(),
|
||||||
|
const FollowAppEvent('username3'),
|
||||||
|
EmptyEvent(),
|
||||||
|
const FollowAppEvent('username2'),
|
||||||
|
EmptyEvent(),
|
||||||
|
]));
|
||||||
|
|
||||||
|
bus.fire(const FollowAppEvent('username'));
|
||||||
|
bus.fire(const FollowAppEvent('username3'));
|
||||||
|
bus.fire(const FollowAppEvent('username2'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('start watch but not complete', () {
|
||||||
|
expect(
|
||||||
|
bus.on(),
|
||||||
|
emitsInOrder([
|
||||||
|
const FollowAppEvent('username'),
|
||||||
|
]));
|
||||||
|
|
||||||
|
bus.watch(const FollowAppEvent('username'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('start watch and complete', () {
|
||||||
|
const watchable = FollowAppEvent('username3');
|
||||||
|
expect(
|
||||||
|
bus.on(),
|
||||||
|
emitsInOrder([
|
||||||
|
watchable,
|
||||||
|
EmptyEvent(),
|
||||||
|
const EventCompletionEvent(watchable),
|
||||||
|
EmptyEvent(),
|
||||||
|
const FollowSuccessfullyEvent(watchable),
|
||||||
|
EmptyEvent(),
|
||||||
|
]));
|
||||||
|
|
||||||
|
bus.watch(watchable);
|
||||||
|
bus.complete(watchable,
|
||||||
|
nextEvent: const FollowSuccessfullyEvent(watchable));
|
||||||
|
});
|
||||||
|
|
||||||
|
// test('emit Follower Events', () {
|
||||||
|
// final watchable = FollowAppEvent('username3', id: '3');
|
||||||
|
// expect(
|
||||||
|
// _bus.on(),
|
||||||
|
// emitsInAnyOrder([
|
||||||
|
// FollowAppEvent('username3', id: '3'),
|
||||||
|
// FollowSuccessfullyAppEvent(watchable),
|
||||||
|
// ]));
|
||||||
|
|
||||||
|
// _bus.watch(watchable);
|
||||||
|
// _bus.complete(watchable, withh: FollowSuccessfullyAppEvent(watchable));
|
||||||
|
// });
|
||||||
|
}
|
58
core/eventbus/test/distinct_test.dart
Normal file
58
core/eventbus/test/distinct_test.dart
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import 'package:event_bus_plus/event_bus_plus.dart';
|
||||||
|
import 'package:given_when_then_unit_test/given_when_then_unit_test.dart';
|
||||||
|
import 'package:test/expect.dart';
|
||||||
|
import 'package:test/scaffolding.dart';
|
||||||
|
|
||||||
|
import 'models.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late IEventBus bus;
|
||||||
|
|
||||||
|
before(() {
|
||||||
|
bus = EventBus();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Call once', () {
|
||||||
|
expectLater(
|
||||||
|
bus.last$,
|
||||||
|
emitsInOrder([
|
||||||
|
const SomeEvent(),
|
||||||
|
EmptyEvent(),
|
||||||
|
const SomeAnotherEvent(),
|
||||||
|
EmptyEvent(),
|
||||||
|
]));
|
||||||
|
bus.fire(const SomeEvent());
|
||||||
|
bus.fire(const SomeAnotherEvent());
|
||||||
|
});
|
||||||
|
|
||||||
|
// test('Call twice', () {
|
||||||
|
// const event = SomeEvent();
|
||||||
|
// expectLater(
|
||||||
|
// bus.last$,
|
||||||
|
// emitsInOrder([
|
||||||
|
// const SomeEvent(),
|
||||||
|
// EmptyEvent(),
|
||||||
|
// const SomeAnotherEvent(),
|
||||||
|
// EmptyEvent(),
|
||||||
|
// ]));
|
||||||
|
// bus.fire(event);
|
||||||
|
// bus.fire(event);
|
||||||
|
// bus.fire(const SomeAnotherEvent());
|
||||||
|
// });
|
||||||
|
|
||||||
|
// test('Call three times', () {
|
||||||
|
// const event = SomeEvent();
|
||||||
|
// expectLater(
|
||||||
|
// bus.last$,
|
||||||
|
// emitsInOrder([
|
||||||
|
// const SomeEvent(),
|
||||||
|
// EmptyEvent(),
|
||||||
|
// const SomeAnotherEvent(),
|
||||||
|
// EmptyEvent(),
|
||||||
|
// ]));
|
||||||
|
// bus.fire(event);
|
||||||
|
// bus.fire(event);
|
||||||
|
// bus.fire(event);
|
||||||
|
// bus.fire(const SomeAnotherEvent());
|
||||||
|
// });
|
||||||
|
}
|
20
core/eventbus/test/empty_event_test.dart
Normal file
20
core/eventbus/test/empty_event_test.dart
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import 'package:event_bus_plus/event_bus_plus.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'models.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
final IEventBus _bus = EventBus();
|
||||||
|
|
||||||
|
test('Should fire Empty event', () {
|
||||||
|
expect(
|
||||||
|
_bus.last$,
|
||||||
|
emitsInOrder(
|
||||||
|
[
|
||||||
|
const SomeEvent(),
|
||||||
|
EmptyEvent(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
_bus.fire(const SomeEvent());
|
||||||
|
}, timeout: const Timeout(Duration(seconds: 1)));
|
||||||
|
}
|
63
core/eventbus/test/event_bus_test.dart
Normal file
63
core/eventbus/test/event_bus_test.dart
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
import 'package:event_bus_plus/event_bus_plus.dart';
|
||||||
|
import 'package:given_when_then_unit_test/given_when_then_unit_test.dart';
|
||||||
|
import 'package:shouldly/shouldly.dart';
|
||||||
|
import 'package:test/scaffolding.dart';
|
||||||
|
|
||||||
|
import 'models.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late IEventBus bus;
|
||||||
|
|
||||||
|
before(() {
|
||||||
|
bus = EventBus();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Empty event bus', () {
|
||||||
|
bus.isBusy.should.beFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
when('start some event', () {
|
||||||
|
const event = FollowAppEvent('username');
|
||||||
|
// const eventId = '1';
|
||||||
|
|
||||||
|
before(() {
|
||||||
|
bus.watch(event);
|
||||||
|
});
|
||||||
|
|
||||||
|
then('should be busy', () {
|
||||||
|
bus.isBusy.should.beTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
then('should be in progress', () {
|
||||||
|
bus.isInProgress<FollowAppEvent>().should.beTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
and('complete the event', () {
|
||||||
|
before(() {
|
||||||
|
bus.complete(event);
|
||||||
|
});
|
||||||
|
|
||||||
|
then('should not be busy', () {
|
||||||
|
bus.isBusy.should.beFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
then('should not be in progress', () {
|
||||||
|
bus.isInProgress<FollowAppEvent>().should.not.beTrue();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('compare equality', () {
|
||||||
|
// test('compare two equal events - should not be equal', () {
|
||||||
|
// final event = FollowAppEvent('username');
|
||||||
|
// final event2 = FollowAppEvent('username');
|
||||||
|
// event.should.not.be(event2);
|
||||||
|
// });
|
||||||
|
|
||||||
|
test('compare two equal events - should be equal', () {
|
||||||
|
const event = FollowAppEvent('username');
|
||||||
|
const event2 = FollowAppEvent('username');
|
||||||
|
event.should.be(event2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
75
core/eventbus/test/history_test.dart
Normal file
75
core/eventbus/test/history_test.dart
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
|
import 'package:event_bus_plus/event_bus_plus.dart';
|
||||||
|
import 'package:given_when_then_unit_test/given_when_then_unit_test.dart';
|
||||||
|
import 'package:shouldly/shouldly.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
import 'models.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late IEventBus bus;
|
||||||
|
|
||||||
|
before(() {
|
||||||
|
bus = EventBus(maxHistoryLength: 3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Keep history', () {
|
||||||
|
final date = DateTime(2022, 9, 1);
|
||||||
|
withClock(Clock.fixed(date), () {
|
||||||
|
bus.fire(const SomeEvent());
|
||||||
|
bus.fire(const SomeAnotherEvent());
|
||||||
|
bus.fire(const FollowAppEvent('@username'));
|
||||||
|
|
||||||
|
bus.history.should.be([
|
||||||
|
EventBusHistoryEntry(const SomeEvent(), date),
|
||||||
|
EventBusHistoryEntry(const SomeAnotherEvent(), date),
|
||||||
|
EventBusHistoryEntry(const FollowAppEvent('@username'), date),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Keep full history without debounce', () {
|
||||||
|
final date = DateTime(2022, 9, 1, 4, 59, 55);
|
||||||
|
withClock(Clock.fixed(date), () {
|
||||||
|
bus.fire(const SomeEvent());
|
||||||
|
bus.fire(const SomeEvent());
|
||||||
|
bus.fire(const SomeAnotherEvent());
|
||||||
|
|
||||||
|
bus.history.should.be([
|
||||||
|
EventBusHistoryEntry(const SomeEvent(), date),
|
||||||
|
EventBusHistoryEntry(const SomeEvent(), date),
|
||||||
|
EventBusHistoryEntry(const SomeAnotherEvent(), date),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Clear history', () {
|
||||||
|
bus.fire(const SomeEvent());
|
||||||
|
bus.fire(const SomeAnotherEvent());
|
||||||
|
|
||||||
|
bus.clearHistory();
|
||||||
|
|
||||||
|
bus.history.should.beEmpty();
|
||||||
|
});
|
||||||
|
|
||||||
|
group('History length', () {
|
||||||
|
test('Add more than max', () {
|
||||||
|
final date = DateTime(2022, 9, 1, 4, 59, 55);
|
||||||
|
|
||||||
|
withClock(Clock.fixed(date), () {
|
||||||
|
bus.fire(const SomeEvent());
|
||||||
|
bus.fire(const SomeAnotherEvent());
|
||||||
|
bus.fire(const FollowAppEvent('@username'));
|
||||||
|
bus.fire(const NewCommentEvent('text'));
|
||||||
|
|
||||||
|
bus.history.length.should.be(3);
|
||||||
|
|
||||||
|
bus.history.should.be([
|
||||||
|
EventBusHistoryEntry(const SomeAnotherEvent(), date),
|
||||||
|
EventBusHistoryEntry(const FollowAppEvent('@username'), date),
|
||||||
|
EventBusHistoryEntry(const NewCommentEvent('text'), date),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
36
core/eventbus/test/mapping/map_ignore_test.dart
Normal file
36
core/eventbus/test/mapping/map_ignore_test.dart
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import 'package:event_bus_plus/event_bus_plus.dart';
|
||||||
|
import 'package:given_when_then_unit_test/given_when_then_unit_test.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
import '../models.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late IEventBus bus;
|
||||||
|
|
||||||
|
before(
|
||||||
|
() {
|
||||||
|
bus = EventBus(
|
||||||
|
map: {
|
||||||
|
SomeEvent: [
|
||||||
|
(e) => e,
|
||||||
|
(e) => const SomeAnotherEvent(),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test('does not emit the same event', () {
|
||||||
|
expect(
|
||||||
|
bus.last$,
|
||||||
|
emitsInOrder(
|
||||||
|
[
|
||||||
|
const SomeEvent(),
|
||||||
|
const SomeAnotherEvent(),
|
||||||
|
EmptyEvent(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
bus.fire(const SomeEvent());
|
||||||
|
}, timeout: const Timeout(Duration(seconds: 1)));
|
||||||
|
}
|
34
core/eventbus/test/mapping/map_test.dart
Normal file
34
core/eventbus/test/mapping/map_test.dart
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import 'package:event_bus_plus/event_bus_plus.dart';
|
||||||
|
import 'package:given_when_then_unit_test/given_when_then_unit_test.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
import '../models.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late IEventBus bus;
|
||||||
|
|
||||||
|
before(
|
||||||
|
() {
|
||||||
|
bus = EventBus(
|
||||||
|
map: {
|
||||||
|
SomeEvent: [
|
||||||
|
(e) => const SomeAnotherEvent(),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test('emits another', () {
|
||||||
|
expect(
|
||||||
|
bus.last$,
|
||||||
|
emitsInOrder(
|
||||||
|
[
|
||||||
|
const SomeEvent(),
|
||||||
|
const SomeAnotherEvent(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
bus.fire(const SomeEvent());
|
||||||
|
}, timeout: const Timeout(Duration(seconds: 1)));
|
||||||
|
}
|
42
core/eventbus/test/models.dart
Normal file
42
core/eventbus/test/models.dart
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import 'package:event_bus_plus/event_bus_plus.dart';
|
||||||
|
|
||||||
|
class FollowAppEvent extends AppEvent {
|
||||||
|
const FollowAppEvent(this.username);
|
||||||
|
|
||||||
|
final String username;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [username];
|
||||||
|
}
|
||||||
|
|
||||||
|
class FollowSuccessfullyEvent extends AppEvent {
|
||||||
|
const FollowSuccessfullyEvent(this.starting);
|
||||||
|
|
||||||
|
final FollowAppEvent starting;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [starting];
|
||||||
|
}
|
||||||
|
|
||||||
|
class NewCommentEvent extends AppEvent {
|
||||||
|
const NewCommentEvent(this.text);
|
||||||
|
|
||||||
|
final String text;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [text];
|
||||||
|
}
|
||||||
|
|
||||||
|
class SomeEvent extends AppEvent {
|
||||||
|
const SomeEvent();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class SomeAnotherEvent extends AppEvent {
|
||||||
|
const SomeAnotherEvent();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [];
|
||||||
|
}
|
74
core/eventbus/test/respond_test.dart
Normal file
74
core/eventbus/test/respond_test.dart
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
// ignore_for_file: prefer_const_constructors
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:event_bus_plus/event_bus_plus.dart';
|
||||||
|
import 'package:given_when_then_unit_test/given_when_then_unit_test.dart';
|
||||||
|
import 'package:test/expect.dart';
|
||||||
|
import 'package:test/scaffolding.dart';
|
||||||
|
|
||||||
|
import 'models.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late IEventBus bus;
|
||||||
|
|
||||||
|
before(() {
|
||||||
|
bus = EventBus();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('emit Some Event', () {
|
||||||
|
final ctrl = StreamController();
|
||||||
|
|
||||||
|
final sub = bus.respond<NewCommentEvent>((event) {
|
||||||
|
log('new comment');
|
||||||
|
ctrl.add(2);
|
||||||
|
}).respond<FollowAppEvent>((event) {
|
||||||
|
log('new follower');
|
||||||
|
ctrl.add(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(ctrl.stream, emitsInOrder([1, 2]));
|
||||||
|
|
||||||
|
bus.fire(FollowAppEvent('username'));
|
||||||
|
bus.fire(NewCommentEvent('comment #1'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('emit Some Events', () {
|
||||||
|
final ctrl = StreamController();
|
||||||
|
|
||||||
|
final sub = bus.respond<NewCommentEvent>((event) {
|
||||||
|
log('new comment');
|
||||||
|
ctrl.add(2);
|
||||||
|
}).respond<FollowAppEvent>((event) {
|
||||||
|
log('new follower');
|
||||||
|
ctrl.add(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(ctrl.stream, emitsInOrder([1, 2, 1]));
|
||||||
|
|
||||||
|
bus.fire(FollowAppEvent('username'));
|
||||||
|
bus.fire(NewCommentEvent('comment #1'));
|
||||||
|
bus.fire(FollowAppEvent('username2'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('emit all Event', () {
|
||||||
|
final ctrl = StreamController();
|
||||||
|
|
||||||
|
final sub = bus.respond<NewCommentEvent>((event) {
|
||||||
|
log('new comment');
|
||||||
|
ctrl.add(2);
|
||||||
|
}).respond<FollowAppEvent>((event) {
|
||||||
|
log('new follower');
|
||||||
|
ctrl.add(1);
|
||||||
|
}).respond((event) {
|
||||||
|
log('event $event');
|
||||||
|
ctrl.add(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(ctrl.stream, emitsInOrder([1, 3, 3, 2, 3, 3]));
|
||||||
|
|
||||||
|
bus.fire(FollowAppEvent('username'));
|
||||||
|
bus.fire(NewCommentEvent('comment #1'));
|
||||||
|
});
|
||||||
|
}
|
56
core/eventbus/test/streams_test.dart
Normal file
56
core/eventbus/test/streams_test.dart
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import 'package:event_bus_plus/event_bus_plus.dart';
|
||||||
|
import 'package:given_when_then_unit_test/given_when_then_unit_test.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'models.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late IEventBus bus;
|
||||||
|
|
||||||
|
before(() {
|
||||||
|
bus = EventBus();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('description', () {
|
||||||
|
bus.inProgress$.map((List<AppEvent> events) =>
|
||||||
|
events.whereType<FollowAppEvent>().isNotEmpty);
|
||||||
|
}, skip: 'should skip');
|
||||||
|
|
||||||
|
test('emit Follower Event', () {
|
||||||
|
const id = 'id';
|
||||||
|
expect(bus.on(), emitsInAnyOrder([const FollowAppEvent('username')]));
|
||||||
|
bus.fire(const FollowAppEvent('username'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('emit Follower Events', () {
|
||||||
|
expect(
|
||||||
|
bus.on(),
|
||||||
|
emitsInOrder([
|
||||||
|
const FollowAppEvent('username'),
|
||||||
|
EmptyEvent(),
|
||||||
|
const FollowAppEvent('username3'),
|
||||||
|
EmptyEvent(),
|
||||||
|
const FollowAppEvent('username2'),
|
||||||
|
EmptyEvent(),
|
||||||
|
]));
|
||||||
|
|
||||||
|
bus.fire(const FollowAppEvent('username'));
|
||||||
|
bus.fire(const FollowAppEvent('username3'));
|
||||||
|
bus.fire(const FollowAppEvent('username2'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('emit New Comment Event', () {
|
||||||
|
expect(
|
||||||
|
bus.on<NewCommentEvent>(),
|
||||||
|
emitsInOrder([
|
||||||
|
const NewCommentEvent('comment #1'),
|
||||||
|
const NewCommentEvent('comment #2'),
|
||||||
|
]));
|
||||||
|
|
||||||
|
bus.fire(const FollowAppEvent('username3'));
|
||||||
|
bus.fire(const FollowAppEvent('username3'));
|
||||||
|
bus.fire(const NewCommentEvent('comment #1'));
|
||||||
|
bus.fire(const FollowAppEvent('username3'));
|
||||||
|
bus.fire(const NewCommentEvent('comment #2'));
|
||||||
|
bus.fire(const FollowAppEvent('username3'));
|
||||||
|
});
|
||||||
|
}
|
36
core/eventbus/test/timestamp_test.dart
Normal file
36
core/eventbus/test/timestamp_test.dart
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
|
import 'package:event_bus_plus/event_bus_plus.dart';
|
||||||
|
import 'package:given_when_then_unit_test/given_when_then_unit_test.dart';
|
||||||
|
import 'package:shouldly/shouldly.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'dart:developer' as dev;
|
||||||
|
|
||||||
|
import 'models.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late IEventBus bus;
|
||||||
|
|
||||||
|
before(() {
|
||||||
|
bus = EventBus(maxHistoryLength: 3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('right timestamp', () {
|
||||||
|
final date = DateTime(2022, 9, 1);
|
||||||
|
withClock(Clock.fixed(date), () {
|
||||||
|
bus.fire(const SomeEvent());
|
||||||
|
bus.fire(const SomeAnotherEvent());
|
||||||
|
bus.fire(const FollowAppEvent('@username'));
|
||||||
|
|
||||||
|
for (final e in bus.history) {
|
||||||
|
e.timestamp.should.be(date);
|
||||||
|
dev.log(e.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
bus.history.should.be([
|
||||||
|
EventBusHistoryEntry(const SomeEvent(), date),
|
||||||
|
EventBusHistoryEntry(const SomeAnotherEvent(), date),
|
||||||
|
EventBusHistoryEntry(const FollowAppEvent('@username'), date),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
13
core/eventbus/tool/coverage.sh
Executable file
13
core/eventbus/tool/coverage.sh
Executable file
|
@ -0,0 +1,13 @@
|
||||||
|
#!/bin/sh
|
||||||
|
cd ..
|
||||||
|
# Generate `coverage/lcov.info` file
|
||||||
|
flutter test --coverage
|
||||||
|
# Generate HTML report
|
||||||
|
# Note: on macOS you need to have lcov installed on your system (`brew install lcov`) to use this:
|
||||||
|
genhtml coverage/lcov.info -o coverage/html
|
||||||
|
# Open the report
|
||||||
|
open coverage/html/index.html
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue