Fix backend: Register Isar in GetIt, remove broken lib download
This commit is contained in:
parent
a26fcc4cfa
commit
736997733a
257 changed files with 16236 additions and 0 deletions
9
.access
Normal file
9
.access
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
main server:
|
||||
ssh root@147.45.152.129
|
||||
|
||||
system:
|
||||
root:jH^W5qHSh,zCQp
|
||||
vscode:AGktOidxrah1KVC0
|
||||
|
||||
forgejo account:
|
||||
cinnabarflower:uS1jOollFpPv82iYcOiv7YHEG28d
|
||||
0
.gitmodules
vendored
Normal file
0
.gitmodules
vendored
Normal file
|
|
@ -67,6 +67,7 @@ void main(List<String> args) async {
|
|||
name: 'db',
|
||||
inspector: args.contains('--debug'),
|
||||
);
|
||||
getIt.registerSingleton<Isar>(isar);
|
||||
configureDependencies();
|
||||
await getIt<MnemoShelf>().initV2(args);
|
||||
|
||||
|
|
|
|||
55
mnemo_cards_web_v2/packages/yx/city-services-pub/.github/workflows/yx_scope_pr.yaml
vendored
Normal file
55
mnemo_cards_web_v2/packages/yx/city-services-pub/.github/workflows/yx_scope_pr.yaml
vendored
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
name: CI for PRs
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "**/yx_scope/**"
|
||||
- "**/.github/**"
|
||||
- "!**.md"
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
ci_checks:
|
||||
name: CI Checks
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
# Check the library against the most recent version and the previous minor version
|
||||
flutter-version: [3.32.4, 3.29.2, 3.27.1, 3.24.5]
|
||||
package:
|
||||
- yx_scope/packages/yx_scope
|
||||
- yx_scope/packages/yx_scope_flutter
|
||||
- yx_scope/packages/yx_scope_linter
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ${{ matrix.package }}
|
||||
shell: bash
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
flutter-version: ${{ matrix.flutter-version }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: flutter pub get
|
||||
|
||||
- name: Check formatting
|
||||
run: dart format --set-exit-if-changed .
|
||||
|
||||
- name: Run linter
|
||||
run: flutter analyze
|
||||
|
||||
- name: Verify custom_lint
|
||||
working-directory: yx_scope/packages/yx_scope_linter/example
|
||||
run: dart run custom_lint --watch
|
||||
|
||||
- name: Run tests
|
||||
if: matrix.package != 'yx_scope/packages/yx_scope_linter'
|
||||
run: flutter test
|
||||
254
mnemo_cards_web_v2/packages/yx/city-services-pub/.mapping.json
Normal file
254
mnemo_cards_web_v2/packages/yx/city-services-pub/.mapping.json
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
{
|
||||
"":"flutter/pro/opensource",
|
||||
".":"flutter/pro/opensource",
|
||||
".github/workflows/yx_scope_pr.yaml":"flutter/pro/opensource/.github/workflows/yx_scope_pr.yaml",
|
||||
"CONTRIBUTING.md":"flutter/pro/opensource/CONTRIBUTING.md",
|
||||
"README.md":"flutter/pro/opensource/README.md",
|
||||
"yx_scope/.gitignore":"flutter/pro/opensource/yx_scope/.gitignore",
|
||||
"yx_scope/AUTHORS":"flutter/pro/opensource/yx_scope/AUTHORS",
|
||||
"yx_scope/CONTRIBUTING.md":"flutter/pro/opensource/yx_scope/CONTRIBUTING.md",
|
||||
"yx_scope/LICENSE":"flutter/pro/opensource/yx_scope/LICENSE",
|
||||
"yx_scope/README.md":"flutter/pro/opensource/yx_scope/README.md",
|
||||
"yx_scope/assets/logos/yx_scope.webp":"flutter/pro/opensource/yx_scope/assets/logos/yx_scope.webp",
|
||||
"yx_scope/packages/yx_scope/.gitignore":"flutter/pro/opensource/yx_scope/packages/yx_scope/.gitignore",
|
||||
"yx_scope/packages/yx_scope/.metadata":"flutter/pro/opensource/yx_scope/packages/yx_scope/.metadata",
|
||||
"yx_scope/packages/yx_scope/CHANGELOG.md":"flutter/pro/opensource/yx_scope/packages/yx_scope/CHANGELOG.md",
|
||||
"yx_scope/packages/yx_scope/LICENSE":"flutter/pro/opensource/yx_scope/packages/yx_scope/LICENSE",
|
||||
"yx_scope/packages/yx_scope/README.md":"flutter/pro/opensource/yx_scope/packages/yx_scope/README.md",
|
||||
"yx_scope/packages/yx_scope/analysis_options.yaml":"flutter/pro/opensource/yx_scope/packages/yx_scope/analysis_options.yaml",
|
||||
"yx_scope/packages/yx_scope/doc/assets/scope_anatomy.png":"flutter/pro/opensource/yx_scope/packages/yx_scope/doc/assets/scope_anatomy.png",
|
||||
"yx_scope/packages/yx_scope/doc/live_templates.md":"flutter/pro/opensource/yx_scope/packages/yx_scope/doc/live_templates.md",
|
||||
"yx_scope/packages/yx_scope/example/.gitignore":"flutter/pro/opensource/yx_scope/packages/yx_scope/example/.gitignore",
|
||||
"yx_scope/packages/yx_scope/example/.metadata":"flutter/pro/opensource/yx_scope/packages/yx_scope/example/.metadata",
|
||||
"yx_scope/packages/yx_scope/example/CHANGELOG.md":"flutter/pro/opensource/yx_scope/packages/yx_scope/example/CHANGELOG.md",
|
||||
"yx_scope/packages/yx_scope/example/LICENSE":"flutter/pro/opensource/yx_scope/packages/yx_scope/example/LICENSE",
|
||||
"yx_scope/packages/yx_scope/example/README.md":"flutter/pro/opensource/yx_scope/packages/yx_scope/example/README.md",
|
||||
"yx_scope/packages/yx_scope/example/analysis_options.yaml":"flutter/pro/opensource/yx_scope/packages/yx_scope/example/analysis_options.yaml",
|
||||
"yx_scope/packages/yx_scope/example/bin/app_listener.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope/example/bin/app_listener.dart",
|
||||
"yx_scope/packages/yx_scope/example/bin/deprecated_listeners_main.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope/example/bin/deprecated_listeners_main.dart",
|
||||
"yx_scope/packages/yx_scope/example/bin/main.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope/example/bin/main.dart",
|
||||
"yx_scope/packages/yx_scope/example/pubspec.yaml":"flutter/pro/opensource/yx_scope/packages/yx_scope/example/pubspec.yaml",
|
||||
"yx_scope/packages/yx_scope/lib/advanced.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope/lib/advanced.dart",
|
||||
"yx_scope/packages/yx_scope/lib/src/base_scope_container.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope/lib/src/base_scope_container.dart",
|
||||
"yx_scope/packages/yx_scope/lib/src/core/async_lifecycle.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope/lib/src/core/async_lifecycle.dart",
|
||||
"yx_scope/packages/yx_scope/lib/src/core/scope_exception.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope/lib/src/core/scope_exception.dart",
|
||||
"yx_scope/packages/yx_scope/lib/src/core/scope_state.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope/lib/src/core/scope_state.dart",
|
||||
"yx_scope/packages/yx_scope/lib/src/core/scope_state_holder.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope/lib/src/core/scope_state_holder.dart",
|
||||
"yx_scope/packages/yx_scope/lib/src/core_scope_holder.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope/lib/src/core_scope_holder.dart",
|
||||
"yx_scope/packages/yx_scope/lib/src/custom_dep.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope/lib/src/custom_dep.dart",
|
||||
"yx_scope/packages/yx_scope/lib/src/dep.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope/lib/src/dep.dart",
|
||||
"yx_scope/packages/yx_scope/lib/src/monitoring/listeners.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope/lib/src/monitoring/listeners.dart",
|
||||
"yx_scope/packages/yx_scope/lib/src/monitoring/models/dep_id.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope/lib/src/monitoring/models/dep_id.dart",
|
||||
"yx_scope/packages/yx_scope/lib/src/monitoring/models/scope_id.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope/lib/src/monitoring/models/scope_id.dart",
|
||||
"yx_scope/packages/yx_scope/lib/src/monitoring/models/scope_meta.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope/lib/src/monitoring/models/scope_meta.dart",
|
||||
"yx_scope/packages/yx_scope/lib/src/monitoring/models/value_meta.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope/lib/src/monitoring/models/value_meta.dart",
|
||||
"yx_scope/packages/yx_scope/lib/src/monitoring/observers.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope/lib/src/monitoring/observers.dart",
|
||||
"yx_scope/packages/yx_scope/lib/src/monitoring/observers_internal.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope/lib/src/monitoring/observers_internal.dart",
|
||||
"yx_scope/packages/yx_scope/lib/src/monitoring/raw_listeners.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope/lib/src/monitoring/raw_listeners.dart",
|
||||
"yx_scope/packages/yx_scope/lib/src/monitoring/raw_observers.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope/lib/src/monitoring/raw_observers.dart",
|
||||
"yx_scope/packages/yx_scope/lib/src/monitoring/scope_observatory.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope/lib/src/monitoring/scope_observatory.dart",
|
||||
"yx_scope/packages/yx_scope/lib/src/monitoring/scope_observatory_internal.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope/lib/src/monitoring/scope_observatory_internal.dart",
|
||||
"yx_scope/packages/yx_scope/lib/src/scope_container.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope/lib/src/scope_container.dart",
|
||||
"yx_scope/packages/yx_scope/lib/src/scope_holder.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope/lib/src/scope_holder.dart",
|
||||
"yx_scope/packages/yx_scope/lib/src/scope_module.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope/lib/src/scope_module.dart",
|
||||
"yx_scope/packages/yx_scope/lib/src/scope_state_streamable.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope/lib/src/scope_state_streamable.dart",
|
||||
"yx_scope/packages/yx_scope/lib/src/test_utils/scope_container_test_utils.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope/lib/src/test_utils/scope_container_test_utils.dart",
|
||||
"yx_scope/packages/yx_scope/lib/src/test_utils/scope_state_test_utils.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope/lib/src/test_utils/scope_state_test_utils.dart",
|
||||
"yx_scope/packages/yx_scope/lib/yx_scope.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope/lib/yx_scope.dart",
|
||||
"yx_scope/packages/yx_scope/pubspec.yaml":"flutter/pro/opensource/yx_scope/packages/yx_scope/pubspec.yaml",
|
||||
"yx_scope/packages/yx_scope/screenshots/yx_scope_logo.webp":"flutter/pro/opensource/yx_scope/packages/yx_scope/screenshots/yx_scope_logo.webp",
|
||||
"yx_scope/packages/yx_scope/test/dep_test.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope/test/dep_test.dart",
|
||||
"yx_scope/packages/yx_scope/test/deprecated_listeners_test.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope/test/deprecated_listeners_test.dart",
|
||||
"yx_scope/packages/yx_scope/test/observers_test.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope/test/observers_test.dart",
|
||||
"yx_scope/packages/yx_scope/test/parent_scope_test.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope/test/parent_scope_test.dart",
|
||||
"yx_scope/packages/yx_scope/test/scope_state_streamable_test.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope/test/scope_state_streamable_test.dart",
|
||||
"yx_scope/packages/yx_scope/test/scope_state_test.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope/test/scope_state_test.dart",
|
||||
"yx_scope/packages/yx_scope/test/scope_test.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope/test/scope_test.dart",
|
||||
"yx_scope/packages/yx_scope/test/utils/test_logger.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope/test/utils/test_logger.dart",
|
||||
"yx_scope/packages/yx_scope/test/utils/utils.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope/test/utils/utils.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/.gitignore":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/.gitignore",
|
||||
"yx_scope/packages/yx_scope_flutter/.metadata":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/.metadata",
|
||||
"yx_scope/packages/yx_scope_flutter/CHANGELOG.md":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/CHANGELOG.md",
|
||||
"yx_scope/packages/yx_scope_flutter/LICENSE":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/LICENSE",
|
||||
"yx_scope/packages/yx_scope_flutter/README.md":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/README.md",
|
||||
"yx_scope/packages/yx_scope_flutter/analysis_options.yaml":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/analysis_options.yaml",
|
||||
"yx_scope/packages/yx_scope_flutter/doc/assets/scope_anatomy.png":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/doc/assets/scope_anatomy.png",
|
||||
"yx_scope/packages/yx_scope_flutter/doc/assets/scope_tree.png":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/doc/assets/scope_tree.png",
|
||||
"yx_scope/packages/yx_scope_flutter/doc/assets/scope_types.png":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/doc/assets/scope_types.png",
|
||||
"yx_scope/packages/yx_scope_flutter/doc/introduction.md":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/doc/introduction.md",
|
||||
"yx_scope/packages/yx_scope_flutter/example/.gitignore":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/.gitignore",
|
||||
"yx_scope/packages/yx_scope_flutter/example/.metadata":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/.metadata",
|
||||
"yx_scope/packages/yx_scope_flutter/example/README.md":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/README.md",
|
||||
"yx_scope/packages/yx_scope_flutter/example/analysis_options.yaml":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/analysis_options.yaml",
|
||||
"yx_scope/packages/yx_scope_flutter/example/lib/data/map/map.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/lib/data/map/map.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/example/lib/data/map/map_widget.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/lib/data/map/map_widget.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/example/lib/data/orders/incoming_orders_provider.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/lib/data/orders/incoming_orders_provider.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/example/lib/data/orders/models/incoming_order_data.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/lib/data/orders/models/incoming_order_data.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/example/lib/di/account/account_scope.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/lib/di/account/account_scope.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/example/lib/di/app/app_scope.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/lib/di/app/app_scope.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/example/lib/di/map/map_scope.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/lib/di/map/map_scope.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/example/lib/di/map_navigation/map_navigation_scope.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/lib/di/map_navigation/map_navigation_scope.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/example/lib/di/online_order/online_scope.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/lib/di/online_order/online_scope.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/example/lib/di/order/order_scope.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/lib/di/order/order_scope.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/example/lib/di/register/register_scope.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/lib/di/register/register_scope.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/example/lib/di/utils/listeners.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/lib/di/utils/listeners.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/example/lib/domain/auth/account_holder.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/lib/domain/auth/account_holder.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/example/lib/domain/auth/auth_manager.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/lib/domain/auth/auth_manager.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/example/lib/domain/auth/models/account.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/lib/domain/auth/models/account.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/example/lib/domain/auth/models/account_params.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/lib/domain/auth/models/account_params.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/example/lib/domain/auth/register_manager.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/lib/domain/auth/register_manager.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/example/lib/domain/auth/register_state_holder.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/lib/domain/auth/register_state_holder.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/example/lib/domain/map/map_factory.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/lib/domain/map/map_factory.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/example/lib/domain/map/map_manager.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/lib/domain/map/map_manager.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/example/lib/domain/map_navigation/map_navigation_manager.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/lib/domain/map_navigation/map_navigation_manager.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/example/lib/domain/order/accept_order_manager.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/lib/domain/order/accept_order_manager.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/example/lib/domain/order/models/order.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/lib/domain/order/models/order.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/example/lib/domain/order/online_order_state_holder.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/lib/domain/order/online_order_state_holder.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/example/lib/domain/order/order_manager.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/lib/domain/order/order_manager.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/example/lib/domain/order/order_position_manager.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/lib/domain/order/order_position_manager.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/example/lib/domain/order/orders_state_holder.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/lib/domain/order/orders_state_holder.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/example/lib/domain/order_navigation/order_navigation_delegate.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/lib/domain/order_navigation/order_navigation_delegate.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/example/lib/main.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/lib/main.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/example/lib/router/app_state_observer.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/lib/router/app_state_observer.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/example/lib/router/models/app_state.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/lib/router/models/app_state.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/example/lib/router/router_delegate.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/lib/router/router_delegate.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/example/lib/ui/account/account_page.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/lib/ui/account/account_page.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/example/lib/ui/auth/auth_page.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/lib/ui/auth/auth_page.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/example/lib/ui/map/map_page.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/lib/ui/map/map_page.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/example/lib/ui/orders/accept_order_wrapper.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/lib/ui/orders/accept_order_wrapper.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/example/lib/ui/orders/orders_page.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/lib/ui/orders/orders_page.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/example/lib/ui/register/register_page.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/lib/ui/register/register_page.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/example/lib/ui/tabbar/tabbar_page.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/lib/ui/tabbar/tabbar_page.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/example/lib/utils/logger.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/lib/utils/logger.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/example/pubspec.yaml":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/pubspec.yaml",
|
||||
"yx_scope/packages/yx_scope_flutter/example/pubspec_overrides.yaml":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/example/pubspec_overrides.yaml",
|
||||
"yx_scope/packages/yx_scope_flutter/lib/src/core/provider.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/lib/src/core/provider.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/lib/src/core/scope_error.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/lib/src/core/scope_error.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/lib/src/scope_builder.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/lib/src/scope_builder.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/lib/src/scope_consumer.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/lib/src/scope_consumer.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/lib/src/scope_listener.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/lib/src/scope_listener.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/lib/src/scope_provider.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/lib/src/scope_provider.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/lib/src/scope_widget_builder.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/lib/src/scope_widget_builder.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/lib/src/scope_widget_listener.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/lib/src/scope_widget_listener.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/lib/yx_scope_flutter.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/lib/yx_scope_flutter.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/pubspec.yaml":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/pubspec.yaml",
|
||||
"yx_scope/packages/yx_scope_flutter/pubspec_overrides.yaml":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/pubspec_overrides.yaml",
|
||||
"yx_scope/packages/yx_scope_flutter/test/scope_builder_test.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/test/scope_builder_test.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/test/scope_listener_test.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/test/scope_listener_test.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/test/scope_provider_test.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/test/scope_provider_test.dart",
|
||||
"yx_scope/packages/yx_scope_flutter/test/test_utils.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_flutter/test/test_utils.dart",
|
||||
"yx_scope/packages/yx_scope_linter/.gitignore":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/.gitignore",
|
||||
"yx_scope/packages/yx_scope_linter/.metadata":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/.metadata",
|
||||
"yx_scope/packages/yx_scope_linter/CHANGELOG.md":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/CHANGELOG.md",
|
||||
"yx_scope/packages/yx_scope_linter/LICENSE":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/LICENSE",
|
||||
"yx_scope/packages/yx_scope_linter/README.md":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/README.md",
|
||||
"yx_scope/packages/yx_scope_linter/analysis_options.yaml":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/analysis_options.yaml",
|
||||
"yx_scope/packages/yx_scope_linter/doc/manual_linter.md":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/doc/manual_linter.md",
|
||||
"yx_scope/packages/yx_scope_linter/example/.gitignore":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/example/.gitignore",
|
||||
"yx_scope/packages/yx_scope_linter/example/.metadata":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/example/.metadata",
|
||||
"yx_scope/packages/yx_scope_linter/example/CHANGELOG.md":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/example/CHANGELOG.md",
|
||||
"yx_scope/packages/yx_scope_linter/example/LICENSE":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/example/LICENSE",
|
||||
"yx_scope/packages/yx_scope_linter/example/README.md":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/example/README.md",
|
||||
"yx_scope/packages/yx_scope_linter/example/analysis_options.yaml":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/example/analysis_options.yaml",
|
||||
"yx_scope/packages/yx_scope_linter/example/lib/avoid_async_dep_child_scope.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/example/lib/avoid_async_dep_child_scope.dart",
|
||||
"yx_scope/packages/yx_scope_linter/example/lib/consider_dep_suffix.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/example/lib/consider_dep_suffix.dart",
|
||||
"yx_scope/packages/yx_scope_linter/example/lib/dep_cycle.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/example/lib/dep_cycle.dart",
|
||||
"yx_scope/packages/yx_scope_linter/example/lib/final_dep.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/example/lib/final_dep.dart",
|
||||
"yx_scope/packages/yx_scope_linter/example/lib/pass_async_lifecycle_in_initialize_queue.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/example/lib/pass_async_lifecycle_in_initialize_queue.dart",
|
||||
"yx_scope/packages/yx_scope_linter/example/lib/use_async_dep_for_async_lifecycle.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/example/lib/use_async_dep_for_async_lifecycle.dart",
|
||||
"yx_scope/packages/yx_scope_linter/example/lib/utils.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/example/lib/utils.dart",
|
||||
"yx_scope/packages/yx_scope_linter/example/pubspec.yaml":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/example/pubspec.yaml",
|
||||
"yx_scope/packages/yx_scope_linter/example/pubspec_overrides.yaml":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/example/pubspec_overrides.yaml",
|
||||
"yx_scope/packages/yx_scope_linter/lib/src/extensions.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/lib/src/extensions.dart",
|
||||
"yx_scope/packages/yx_scope_linter/lib/src/lints/avoid_async_dep_child_scope.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/lib/src/lints/avoid_async_dep_child_scope.dart",
|
||||
"yx_scope/packages/yx_scope_linter/lib/src/lints/consider_dep_suffix.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/lib/src/lints/consider_dep_suffix.dart",
|
||||
"yx_scope/packages/yx_scope_linter/lib/src/lints/dep_cycle.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/lib/src/lints/dep_cycle.dart",
|
||||
"yx_scope/packages/yx_scope_linter/lib/src/lints/final_dep.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/lib/src/lints/final_dep.dart",
|
||||
"yx_scope/packages/yx_scope_linter/lib/src/lints/pass_async_lifecycle_in_initialize_queue.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/lib/src/lints/pass_async_lifecycle_in_initialize_queue.dart",
|
||||
"yx_scope/packages/yx_scope_linter/lib/src/lints/use_async_dep_for_async_lifecycle.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/lib/src/lints/use_async_dep_for_async_lifecycle.dart",
|
||||
"yx_scope/packages/yx_scope_linter/lib/src/models/dep.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/lib/src/models/dep.dart",
|
||||
"yx_scope/packages/yx_scope_linter/lib/src/models/exceptions.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/lib/src/models/exceptions.dart",
|
||||
"yx_scope/packages/yx_scope_linter/lib/src/names.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/lib/src/names.dart",
|
||||
"yx_scope/packages/yx_scope_linter/lib/src/plugin.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/lib/src/plugin.dart",
|
||||
"yx_scope/packages/yx_scope_linter/lib/src/priority.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/lib/src/priority.dart",
|
||||
"yx_scope/packages/yx_scope_linter/lib/src/resolved_yx_scope_result.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/lib/src/resolved_yx_scope_result.dart",
|
||||
"yx_scope/packages/yx_scope_linter/lib/src/types.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/lib/src/types.dart",
|
||||
"yx_scope/packages/yx_scope_linter/lib/src/utils.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/lib/src/utils.dart",
|
||||
"yx_scope/packages/yx_scope_linter/lib/src/visitors/parse_dependencies_for_dep_visitor.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/lib/src/visitors/parse_dependencies_for_dep_visitor.dart",
|
||||
"yx_scope/packages/yx_scope_linter/lib/src/visitors/parse_initialize_queue_visitor.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/lib/src/visitors/parse_initialize_queue_visitor.dart",
|
||||
"yx_scope/packages/yx_scope_linter/lib/src/visitors/parse_scope_declaration_visitor.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/lib/src/visitors/parse_scope_declaration_visitor.dart",
|
||||
"yx_scope/packages/yx_scope_linter/lib/src/visitors/parse_visitor.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/lib/src/visitors/parse_visitor.dart",
|
||||
"yx_scope/packages/yx_scope_linter/lib/src/yx_scope_lint_rule.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/lib/src/yx_scope_lint_rule.dart",
|
||||
"yx_scope/packages/yx_scope_linter/lib/src/yx_scope_registry.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/lib/src/yx_scope_registry.dart",
|
||||
"yx_scope/packages/yx_scope_linter/lib/yx_scope_linter.dart":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/lib/yx_scope_linter.dart",
|
||||
"yx_scope/packages/yx_scope_linter/pubspec.yaml":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/pubspec.yaml",
|
||||
"yx_scope/packages/yx_scope_linter/pubspec_overrides.yaml":"flutter/pro/opensource/yx_scope/packages/yx_scope_linter/pubspec_overrides.yaml",
|
||||
"yx_state/.gitignore":"flutter/pro/opensource/yx_state/.gitignore",
|
||||
"yx_state/AUTHORS":"flutter/pro/opensource/yx_state/AUTHORS",
|
||||
"yx_state/CONTRIBUTING.md":"flutter/pro/opensource/yx_state/CONTRIBUTING.md",
|
||||
"yx_state/LICENSE":"flutter/pro/opensource/yx_state/LICENSE",
|
||||
"yx_state/README.md":"flutter/pro/opensource/yx_state/README.md",
|
||||
"yx_state/analysis_options.yaml":"flutter/pro/opensource/yx_state/analysis_options.yaml",
|
||||
"yx_state/assets/logos/yx_state.webp":"flutter/pro/opensource/yx_state/assets/logos/yx_state.webp",
|
||||
"yx_state/packages/yx_state/.gitignore":"flutter/pro/opensource/yx_state/packages/yx_state/.gitignore",
|
||||
"yx_state/packages/yx_state/AUTHORS":"flutter/pro/opensource/yx_state/packages/yx_state/AUTHORS",
|
||||
"yx_state/packages/yx_state/CHANGELOG.md":"flutter/pro/opensource/yx_state/packages/yx_state/CHANGELOG.md",
|
||||
"yx_state/packages/yx_state/CONTRIBUTING.md":"flutter/pro/opensource/yx_state/packages/yx_state/CONTRIBUTING.md",
|
||||
"yx_state/packages/yx_state/LICENSE":"flutter/pro/opensource/yx_state/packages/yx_state/LICENSE",
|
||||
"yx_state/packages/yx_state/README.md":"flutter/pro/opensource/yx_state/packages/yx_state/README.md",
|
||||
"yx_state/packages/yx_state/analysis_options.yaml":"flutter/pro/opensource/yx_state/packages/yx_state/analysis_options.yaml",
|
||||
"yx_state/packages/yx_state/example/main.dart":"flutter/pro/opensource/yx_state/packages/yx_state/example/main.dart",
|
||||
"yx_state/packages/yx_state/lib/src/base/interface.dart":"flutter/pro/opensource/yx_state/packages/yx_state/lib/src/base/interface.dart",
|
||||
"yx_state/packages/yx_state/lib/src/base/state_manager_base.dart":"flutter/pro/opensource/yx_state/packages/yx_state/lib/src/base/state_manager_base.dart",
|
||||
"yx_state/packages/yx_state/lib/src/function_stream_handler/handle_task.dart":"flutter/pro/opensource/yx_state/packages/yx_state/lib/src/function_stream_handler/handle_task.dart",
|
||||
"yx_state/packages/yx_state/lib/src/function_stream_handler/handle_task_emitter.dart":"flutter/pro/opensource/yx_state/packages/yx_state/lib/src/function_stream_handler/handle_task_emitter.dart",
|
||||
"yx_state/packages/yx_state/lib/src/function_stream_handler/stream_function_handler.dart":"flutter/pro/opensource/yx_state/packages/yx_state/lib/src/function_stream_handler/stream_function_handler.dart",
|
||||
"yx_state/packages/yx_state/lib/src/mixin/state_manager_listener_mixin.dart":"flutter/pro/opensource/yx_state/packages/yx_state/lib/src/mixin/state_manager_listener_mixin.dart",
|
||||
"yx_state/packages/yx_state/lib/src/state_manager.dart":"flutter/pro/opensource/yx_state/packages/yx_state/lib/src/state_manager.dart",
|
||||
"yx_state/packages/yx_state/lib/src/state_manager_observer.dart":"flutter/pro/opensource/yx_state/packages/yx_state/lib/src/state_manager_observer.dart",
|
||||
"yx_state/packages/yx_state/lib/src/state_manager_overrides.dart":"flutter/pro/opensource/yx_state/packages/yx_state/lib/src/state_manager_overrides.dart",
|
||||
"yx_state/packages/yx_state/lib/src/test_util/state_manager_base_test_util.dart":"flutter/pro/opensource/yx_state/packages/yx_state/lib/src/test_util/state_manager_base_test_util.dart",
|
||||
"yx_state/packages/yx_state/lib/yx_state.dart":"flutter/pro/opensource/yx_state/packages/yx_state/lib/yx_state.dart",
|
||||
"yx_state/packages/yx_state/pubspec.yaml":"flutter/pro/opensource/yx_state/packages/yx_state/pubspec.yaml",
|
||||
"yx_state/packages/yx_state/screenshots/yx_state_logo.webp":"flutter/pro/opensource/yx_state/packages/yx_state/screenshots/yx_state_logo.webp",
|
||||
"yx_state/packages/yx_state/test/src/function_stream_handler/handle_task_emitter_test.dart":"flutter/pro/opensource/yx_state/packages/yx_state/test/src/function_stream_handler/handle_task_emitter_test.dart",
|
||||
"yx_state/packages/yx_state/test/src/function_stream_handler/handle_task_test.dart":"flutter/pro/opensource/yx_state/packages/yx_state/test/src/function_stream_handler/handle_task_test.dart",
|
||||
"yx_state/packages/yx_state/test/src/function_stream_handler/stream_function_handler_test.dart":"flutter/pro/opensource/yx_state/packages/yx_state/test/src/function_stream_handler/stream_function_handler_test.dart",
|
||||
"yx_state/packages/yx_state/test/src/mixin/state_manager_listener_mixin_test.dart":"flutter/pro/opensource/yx_state/packages/yx_state/test/src/mixin/state_manager_listener_mixin_test.dart",
|
||||
"yx_state/packages/yx_state/test/src/mocks.dart":"flutter/pro/opensource/yx_state/packages/yx_state/test/src/mocks.dart",
|
||||
"yx_state/packages/yx_state/test/src/state_manager_test.dart":"flutter/pro/opensource/yx_state/packages/yx_state/test/src/state_manager_test.dart",
|
||||
"yx_state/packages/yx_state_flutter/.gitignore":"flutter/pro/opensource/yx_state/packages/yx_state_flutter/.gitignore",
|
||||
"yx_state/packages/yx_state_flutter/AUTHORS":"flutter/pro/opensource/yx_state/packages/yx_state_flutter/AUTHORS",
|
||||
"yx_state/packages/yx_state_flutter/CHANGELOG.md":"flutter/pro/opensource/yx_state/packages/yx_state_flutter/CHANGELOG.md",
|
||||
"yx_state/packages/yx_state_flutter/CONTRIBUTING.md":"flutter/pro/opensource/yx_state/packages/yx_state_flutter/CONTRIBUTING.md",
|
||||
"yx_state/packages/yx_state_flutter/LICENSE":"flutter/pro/opensource/yx_state/packages/yx_state_flutter/LICENSE",
|
||||
"yx_state/packages/yx_state_flutter/README.md":"flutter/pro/opensource/yx_state/packages/yx_state_flutter/README.md",
|
||||
"yx_state/packages/yx_state_flutter/analysis_options.yaml":"flutter/pro/opensource/yx_state/packages/yx_state_flutter/analysis_options.yaml",
|
||||
"yx_state/packages/yx_state_flutter/example/.gitignore":"flutter/pro/opensource/yx_state/packages/yx_state_flutter/example/.gitignore",
|
||||
"yx_state/packages/yx_state_flutter/example/README.md":"flutter/pro/opensource/yx_state/packages/yx_state_flutter/example/README.md",
|
||||
"yx_state/packages/yx_state_flutter/example/analysis_options.yaml":"flutter/pro/opensource/yx_state/packages/yx_state_flutter/example/analysis_options.yaml",
|
||||
"yx_state/packages/yx_state_flutter/example/lib/main.dart":"flutter/pro/opensource/yx_state/packages/yx_state_flutter/example/lib/main.dart",
|
||||
"yx_state/packages/yx_state_flutter/example/pubspec.yaml":"flutter/pro/opensource/yx_state/packages/yx_state_flutter/example/pubspec.yaml",
|
||||
"yx_state/packages/yx_state_flutter/example/pubspec_overrides.yaml":"flutter/pro/opensource/yx_state/packages/yx_state_flutter/example/pubspec_overrides.yaml",
|
||||
"yx_state/packages/yx_state_flutter/lib/src/state_builder.dart":"flutter/pro/opensource/yx_state/packages/yx_state_flutter/lib/src/state_builder.dart",
|
||||
"yx_state/packages/yx_state_flutter/lib/src/state_consumer.dart":"flutter/pro/opensource/yx_state/packages/yx_state_flutter/lib/src/state_consumer.dart",
|
||||
"yx_state/packages/yx_state_flutter/lib/src/state_listener.dart":"flutter/pro/opensource/yx_state/packages/yx_state_flutter/lib/src/state_listener.dart",
|
||||
"yx_state/packages/yx_state_flutter/lib/src/state_selector.dart":"flutter/pro/opensource/yx_state/packages/yx_state_flutter/lib/src/state_selector.dart",
|
||||
"yx_state/packages/yx_state_flutter/lib/src/typedefs.dart":"flutter/pro/opensource/yx_state/packages/yx_state_flutter/lib/src/typedefs.dart",
|
||||
"yx_state/packages/yx_state_flutter/lib/yx_state_flutter.dart":"flutter/pro/opensource/yx_state/packages/yx_state_flutter/lib/yx_state_flutter.dart",
|
||||
"yx_state/packages/yx_state_flutter/pubspec.yaml":"flutter/pro/opensource/yx_state/packages/yx_state_flutter/pubspec.yaml",
|
||||
"yx_state/packages/yx_state_flutter/pubspec_overrides.yaml":"flutter/pro/opensource/yx_state/packages/yx_state_flutter/pubspec_overrides.yaml",
|
||||
"yx_state/packages/yx_state_transformers/.gitignore":"flutter/pro/opensource/yx_state/packages/yx_state_transformers/.gitignore",
|
||||
"yx_state/packages/yx_state_transformers/AUTHORS":"flutter/pro/opensource/yx_state/packages/yx_state_transformers/AUTHORS",
|
||||
"yx_state/packages/yx_state_transformers/CHANGELOG.md":"flutter/pro/opensource/yx_state/packages/yx_state_transformers/CHANGELOG.md",
|
||||
"yx_state/packages/yx_state_transformers/CONTRIBUTING.md":"flutter/pro/opensource/yx_state/packages/yx_state_transformers/CONTRIBUTING.md",
|
||||
"yx_state/packages/yx_state_transformers/LICENSE":"flutter/pro/opensource/yx_state/packages/yx_state_transformers/LICENSE",
|
||||
"yx_state/packages/yx_state_transformers/README.md":"flutter/pro/opensource/yx_state/packages/yx_state_transformers/README.md",
|
||||
"yx_state/packages/yx_state_transformers/analysis_options.yaml":"flutter/pro/opensource/yx_state/packages/yx_state_transformers/analysis_options.yaml",
|
||||
"yx_state/packages/yx_state_transformers/example/main.dart":"flutter/pro/opensource/yx_state/packages/yx_state_transformers/example/main.dart",
|
||||
"yx_state/packages/yx_state_transformers/lib/src/function_handlers.dart":"flutter/pro/opensource/yx_state/packages/yx_state_transformers/lib/src/function_handlers.dart",
|
||||
"yx_state/packages/yx_state_transformers/lib/src/handle_task_transformers.dart":"flutter/pro/opensource/yx_state/packages/yx_state_transformers/lib/src/handle_task_transformers.dart",
|
||||
"yx_state/packages/yx_state_transformers/lib/yx_state_transformers.dart":"flutter/pro/opensource/yx_state/packages/yx_state_transformers/lib/yx_state_transformers.dart",
|
||||
"yx_state/packages/yx_state_transformers/pubspec.yaml":"flutter/pro/opensource/yx_state/packages/yx_state_transformers/pubspec.yaml",
|
||||
"yx_state/packages/yx_state_transformers/pubspec_overrides.yaml":"flutter/pro/opensource/yx_state/packages/yx_state_transformers/pubspec_overrides.yaml"
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"project":"city-services-pub",
|
||||
"repository":"arcadia"
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
## Notice to external contributors
|
||||
### General info
|
||||
Hello! In order for us (YANDEX LLC) to accept patches and other contributions from you, you will have to adopt our Contributor License Agreement (the “CLA”). The current version of the CLA you may find here:
|
||||
|
||||
* https://yandex.ru/legal/cla/?lang=en (in English)
|
||||
* https://yandex.ru/legal/cla/?lang=ru (in Russian).
|
||||
|
||||
By adopting the CLA, you state the following:
|
||||
|
||||
* You obviously wish and are willingly licensing your contributions to us for our open source projects under the terms of the CLA,
|
||||
* You have read the terms and conditions of the CLA and agree with them in full,
|
||||
* You are legally able to provide and license your contributions as stated,
|
||||
* We may use your contributions for our open source projects and for any other our project too,
|
||||
* We rely on your assurances concerning the rights of third parties in relation to your contributions.
|
||||
|
||||
If you agree with these principles, please read and adopt our CLA. By providing us your contributions, you hereby declare that you have read and adopted our CLA, and we may freely merge your contributions with our corresponding open source project and use it in further in accordance with terms and conditions of the CLA.
|
||||
|
||||
### Provide contributions
|
||||
If you have adopted terms and conditions of the CLA, you are able to provide your contributions. When you submit your pull request, please add the following information into it:
|
||||
|
||||
```
|
||||
I hereby agree to the terms of the CLA available at: [link].
|
||||
```
|
||||
|
||||
Replace the bracketed text as follows:
|
||||
|
||||
* [link] is the link at the current version of the CLA (you may add here a link https://yandex.ru/legal/cla/?lang=en (in English) or a link https://yandex.ru/legal/cla/?lang=ru (in Russian).
|
||||
It is enough to provide us with such notification once.
|
||||
|
||||
### Other questions
|
||||
If you have any questions, please write us at opensource@yandex-team.ru.
|
||||
19
mnemo_cards_web_v2/packages/yx/city-services-pub/README.md
Normal file
19
mnemo_cards_web_v2/packages/yx/city-services-pub/README.md
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# City Services Flutter™ Libraries
|
||||
|
||||
This repository contains a collection of Flutter™ libraries developed by Yandex City Services. These libraries provide reusable components and utilities for building Flutter mobile applications.
|
||||
|
||||
## Usage
|
||||
|
||||
Each library contains its own documentation and usage examples. Please refer to individual directories for specific implementation details.
|
||||
|
||||
## **Contributing**
|
||||
|
||||
We welcome contributions from the community. Please read our contributing guidelines before submitting pull requests.
|
||||
|
||||
## **License**
|
||||
|
||||
Please check individual package licenses for specific terms and conditions.
|
||||
|
||||
---
|
||||
|
||||
**Disclaimer:** Flutter and the related logo are trademarks of Google LLC. We are not endorsed by or affiliated with Google LLC.
|
||||
30
mnemo_cards_web_v2/packages/yx/city-services-pub/yx_scope/.gitignore
vendored
Normal file
30
mnemo_cards_web_v2/packages/yx/city-services-pub/yx_scope/.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/
|
||||
.arcignore
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
The following authors have created the source code of "yx_scope" published and distributed by YANDEX LLC as the owner:
|
||||
kltsv
|
||||
FedorZavalnyj
|
||||
wupididu
|
||||
|
||||
The following authors have licensed their contributions to YANDEX LLC and everyone who uses "yx_scope" under the licensing terms detailed in LICENSE available at https://github.com/yandex/yx_scope/blob/main/LICENSE.
|
||||
kltsv
|
||||
FedorZavalnyj
|
||||
wupididu
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
## Notice to external contributors
|
||||
### General info
|
||||
Hello! In order for us (YANDEX LLC) to accept patches and other contributions from you, you will have to adopt our Contributor License Agreement (the “CLA”). The current version of the CLA you may find here:
|
||||
|
||||
* https://yandex.ru/legal/cla/?lang=en (in English)
|
||||
* https://yandex.ru/legal/cla/?lang=ru (in Russian).
|
||||
|
||||
By adopting the CLA, you state the following:
|
||||
|
||||
* You obviously wish and are willingly licensing your contributions to us for our open source projects under the terms of the CLA,
|
||||
* You have read the terms and conditions of the CLA and agree with them in full,
|
||||
* You are legally able to provide and license your contributions as stated,
|
||||
* We may use your contributions for our open source projects and for any other our project too,
|
||||
* We rely on your assurances concerning the rights of third parties in relation to your contributions.
|
||||
|
||||
If you agree with these principles, please read and adopt our CLA. By providing us your contributions, you hereby declare that you have read and adopted our CLA, and we may freely merge your contributions with our corresponding open source project and use it in further in accordance with terms and conditions of the CLA.
|
||||
|
||||
### Provide contributions
|
||||
If you have adopted terms and conditions of the CLA, you are able to provide your contributions. When you submit your pull request, please add the following information into it:
|
||||
|
||||
```
|
||||
I hereby agree to the terms of the CLA available at: [link].
|
||||
```
|
||||
|
||||
Replace the bracketed text as follows:
|
||||
|
||||
* [link] is the link at the current version of the CLA (you may add here a link https://yandex.ru/legal/cla/?lang=en (in English) or a link https://yandex.ru/legal/cla/?lang=ru (in Russian).
|
||||
It is enough to provide us with such notification once.
|
||||
|
||||
### Other questions
|
||||
If you have any questions, please write us at opensource@yandex-team.ru.
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2024 YANDEX LLC
|
||||
|
||||
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.
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
# yx_scope packages
|
||||
|
||||
yx_scope is a compile-safe DI framework with advanced scoping capabilities.
|
||||
|
||||
## Library Components
|
||||
|
||||
The library group currently consists of:
|
||||
|
||||
- **[yx_scope](packages/yx_scope)**: The core implementation of the framework
|
||||
- **[yx_scope_flutter](packages/yx_scope_flutter)**: An adapter library that allows embedding
|
||||
yx_scope containers into the widget tree
|
||||
- **[yx_scope_linter](packages/yx_scope_linter)**: A set of custom lint rules that provide
|
||||
additional protection against errors when working with yx_scope
|
||||
|
||||
## Features
|
||||
|
||||
- Pure Dart
|
||||
- DI-like (not static and not ServiceLocator)
|
||||
- Compile-safe access to dependencies
|
||||
- No code generation
|
||||
- Flutter-friendly container management
|
||||
- Declarative description of the dependency tree
|
||||
- Non-reactive dependency tree
|
||||
- Unambiguous behavior and lifecycle of dependencies in containers
|
||||
- Ability to create scopes of any nesting level
|
||||
- Compile-safe check for the existence of active scopes
|
||||
- Support for asynchronous dependencies and their initialization
|
||||
- Compile-safe protection against circular dependencies
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 63 KiB |
39
mnemo_cards_web_v2/packages/yx/city-services-pub/yx_scope/packages/yx_scope/.gitignore
vendored
Normal file
39
mnemo_cards_web_v2/packages/yx/city-services-pub/yx_scope/packages/yx_scope/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# Ignoring native folders of the example as they can be re-generated easily using:
|
||||
# flutter create --platforms=android,ios,web,windows,macos .
|
||||
example/android/
|
||||
example/ios/
|
||||
example/web/
|
||||
example/windows/
|
||||
example/macos/
|
||||
example/linux/
|
||||
|
||||
# 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
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
.packages
|
||||
build/
|
||||
/pubspec.lock
|
||||
|
||||
.arcignore
|
||||
|
|
@ -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: cfa03f886884e42676f1834864657721a1219624
|
||||
channel: stable
|
||||
|
||||
project_type: package
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
## 1.1.3 - unpublished
|
||||
* Added CustomDep/CustomAsyncDep classes for creating custom dependency types
|
||||
|
||||
## 1.1.2 - 2025.08.01
|
||||
|
||||
* Fix: ScopeListners/DepListeners/AsyncDepListeners usage
|
||||
|
||||
## 1.1.1 - 2025.07.08
|
||||
|
||||
* Documentation links fixed
|
||||
|
||||
## 1.1.0 - 2025.07.07
|
||||
|
||||
* Public sealed scope state API added
|
||||
* Fix: Prohibit DataScope initialization with null data by enforcing non-nullable Data type
|
||||
* Documentation fixes and updates
|
||||
* Updated CI with new Flutter version support
|
||||
|
||||
#### Deprecated API
|
||||
Listeners have been renamed into Observers due to conflicts with yx_scope_flutter package.
|
||||
Consider using Observers instead of Listeners.
|
||||
Deprecated classes and fields will be highlighted from now on.
|
||||
In the next major, deprecated API will be completely removed.
|
||||
|
||||
## 1.0.2 - 2024.12.18
|
||||
|
||||
* Fix: corrected an inheritance of DataScopeContainer class to BaseScopeContainer instead of
|
||||
ScopeContainer
|
||||
* Updated repository link
|
||||
* Library topics added
|
||||
|
||||
## 1.0.1 - 2024.10.23
|
||||
|
||||
* Minor fixes in formatting and links
|
||||
|
||||
## 1.0.0 - 2024.10.18
|
||||
|
||||
* Remove redundant register/unregister listener methods
|
||||
* Ready to be open-source
|
||||
|
||||
## 0.1.4 - 2024.10.10
|
||||
|
||||
* yx_scoped -> yx_scope
|
||||
|
||||
## 0.1.3 - 2024.09.23
|
||||
|
||||
* AsyncDep init/dispose callback now are hidden from API
|
||||
* @nonVirtual nonVirtual for dep/asyncDep/rawAsyncDep methods
|
||||
|
||||
## 0.1.2 - 2024.08.20
|
||||
|
||||
* Added optional `name`
|
||||
for `ScopeContainer`, `ChildScopeContainer`, `DataScopeContainer`, `ChildDataScopeContainer`
|
||||
|
||||
## 0.1.1 - 2024.05.27
|
||||
|
||||
* Generic type of container for ScopeHolder's changed for strict link ScopedHolder's with
|
||||
ScopeContainer's
|
||||
|
||||
## 0.1.0 — 2024.05.08
|
||||
|
||||
1. All -Node entities has been renamed to -Container
|
||||
2. Reorder parent and data args in createContainer methods
|
||||
|
||||
## 0.0.4 — 2024.03.09
|
||||
|
||||
Core entities has been renamed:
|
||||
|
||||
1. ScopeModule -> BaseScopeNode
|
||||
2. FeatureScopeModule -> ScopeModule
|
||||
3. RootScopeModule -> ScopeNode
|
||||
4. DataChildScopeModule -> ChildDataScopeModule
|
||||
|
||||
And all their children has been renamed accordingly.
|
||||
|
||||
## 0.0.3 — 2024.03.04
|
||||
|
||||
* New class available: FeatureScopeModule. It helps to decompose ScopeModule into a number of
|
||||
features.
|
||||
* Unnecessary build.yaml removed
|
||||
|
||||
## 0.0.2 — 2024.02.28
|
||||
|
||||
* Parent scope can be defined as an abstract interface that implements BaseScope. It helps to unbind
|
||||
child from knowing an exact implementation of the parent and depend only on it's interface.
|
||||
|
||||
## 0.0.1 — 2024.02.04
|
||||
|
||||
* Initial release
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2024 YANDEX LLC
|
||||
|
||||
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.
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
## yx_scope
|
||||
|
||||
<div align="center">
|
||||
<img src="https://github.com/yandex/city-services-pub/blob/main/yx_scope/assets/logos/yx_scope.webp?raw=true" width="200" alt="The yx_scope package logo"/>
|
||||
|
||||
**yx_scope is a compile-safe DI framework with advanced scoping capabilities.**
|
||||
|
||||
[](https://pub.dev/packages/yx_scope)
|
||||
</div>
|
||||
|
||||
## Library Components
|
||||
|
||||
The library group currently consists of:
|
||||
|
||||
- **yx_scope**: The core implementation of the framework
|
||||
- **yx_scope_flutter**: An adapter library that allows embedding yx_scope containers into the widget
|
||||
tree
|
||||
- **yx_scope_linter**: A set of custom lint rules that provide additional protection against errors
|
||||
when working with yx_scope
|
||||
|
||||
## Features
|
||||
|
||||
- Pure Dart
|
||||
- DI-like (not static and not ServiceLocator)
|
||||
- Compile-safe access to dependencies
|
||||
- No code generation
|
||||
- Flutter-friendly container management
|
||||
- Declarative description of the dependency tree
|
||||
- Non-reactive dependency tree
|
||||
- Unambiguous behavior and lifecycle of dependencies in containers
|
||||
- Ability to create scopes of any nesting level
|
||||
- Compile-safe check for the existence of active scopes
|
||||
- Support for asynchronous dependencies and their initialization
|
||||
- Compile-safe protection against circular dependencies
|
||||
|
||||
## Quick Start
|
||||
|
||||
Let's look at a simple dependency container. First, add yx_scope to your pubspec.yaml:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
yx_scope: ^1.0.0
|
||||
```
|
||||
|
||||
Create a file named `app_scope.dart` and add the description of our container and dependencies:
|
||||
|
||||
```dart
|
||||
class AppScopeContainer extends ScopeContainer {
|
||||
late final routerDelegateDep = dep(() => AppRouterDelegate());
|
||||
|
||||
late final appStateObserverDep = dep(
|
||||
() =>
|
||||
AppStateObserver(
|
||||
routerDelegateDep.get,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class AppScopeHolder extends ScopeHolder<AppScopeContainer> {
|
||||
@override
|
||||
AppScopeContainer createContainer() => AppScopeContainer();
|
||||
}
|
||||
```
|
||||
|
||||
Now, let's create an `AppScopeHolder`, create a container, and access the dependencies:
|
||||
|
||||
```dart
|
||||
void main() async {
|
||||
final appScopeHolder = AppScopeHolder();
|
||||
await appScopeHolder.create();
|
||||
|
||||
final appScope = appScopeHolder.scope;
|
||||
if (appScope != null) {
|
||||
final AppStateObserver appStateObserver = appScope.appStateObserverDep.get;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
An important feature of the library is that we work with the DI container without binding it to the
|
||||
UI. The DI container is primary, and only as an addition to this, the container can be attached to
|
||||
the UI.
|
||||
|
||||
The DI container is created as a reaction to an event in the application's logic, not as a result of
|
||||
the appearance of some screen or UI element. UI does not generate scopes; scopes generate UI.
|
||||
|
||||
This is an important principle of the mechanics of yx_scope.
|
||||
|
||||
## Key Entities
|
||||
|
||||
- **Dep (dependency)**: A container for one specific instance of any entity.
|
||||
- **ScopeContainer**: An isolated, non-overlapping set of dependencies united by a meaningful scope
|
||||
and sharing a common lifecycle.
|
||||
- **ScopeHolder**: An instance that stores the current state of the container and is responsible for
|
||||
its initialization and disposal.
|
||||
|
||||
> A ScopeContainer can be closed by some public interface, hiding implementation details and access
|
||||
> to Dep. In this case, the interface for it will have the suffix Scope, for example, AccountScope.
|
||||
|
||||
`ScopeHolder` is responsible for creating and removing the scope using the `create` and `drop`
|
||||
methods.
|
||||
|
||||
Initially, `ScopeHolder` contains a null state.
|
||||
|
||||
After calling the `create` method, a scope appears - a container with dependencies that can be
|
||||
accessed through the `ScopeHolder`.
|
||||
|
||||
After the `drop` method, all scope dependencies are disposed - the `ScopeHolder` again contains
|
||||
null.
|
||||
|
||||
Due to null-safety, `ScopeHolder` provides a compile-safe check for the existence of a scope
|
||||
directly
|
||||
at the time of writing code, not at runtime.
|
||||
|
||||

|
||||
|
||||
## Live Templates
|
||||
|
||||
You can write code of scopes faster with Live Templates for Intellij IDEA/Android Studio or VSCode.
|
||||
|
||||
You can find an
|
||||
instruction [in the documentation](https://github.com/yandex/city-services-pub/blob/main/yx_scope/packages/yx_scope/doc/live_templates.md).
|
||||
|
|
@ -0,0 +1 @@
|
|||
include: package:lints/recommended.yaml
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
|
|
@ -0,0 +1,264 @@
|
|||
# Intellij IDEA / Android Studio
|
||||
|
||||
1. Go to IntelliJ IDEA > Settings (or IntelliJ IDEA > Preferences on macOS) to open the Settings.
|
||||
2. In the Settings window, navigate to Editor > Live Templates.
|
||||
3. Click Add (plus button), select Template Group and give it a name "yx_scope".
|
||||
4. Select created group and click Add button one more time — select Live Template.
|
||||
5. For each template below repeat the following steps
|
||||
6. Add an Abbreviation (the title of the snippet below) and copy the snippet to the Template Text.
|
||||
7. There is a text "No applicable contexts" — click "Define" and select Dart.
|
||||
|
||||
Now you can type abbreviations in any file of your project and apply the snippet by clicking Enter.
|
||||
|
||||
## scope
|
||||
|
||||
```dart
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:yx_scope/yx_scope.dart';
|
||||
|
||||
class $NAME$ScopeContainer extends ScopeContainer {
|
||||
// TODO deps
|
||||
}
|
||||
|
||||
class $NAME$ScopeHolder extends ScopeHolder<$NAME$ScopeContainer> {
|
||||
@protected
|
||||
@override
|
||||
$NAME$ScopeContainer createContainer() => $NAME$ScopeContainer();
|
||||
}
|
||||
```
|
||||
|
||||
## scope_child
|
||||
|
||||
```dart
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:yx_scope/yx_scope.dart';
|
||||
|
||||
class $NAME$ScopeContainer extends ChildScopeContainer<$PARENTNAME$> {
|
||||
$NAME$ScopeContainer({required $PARENTNAME$ parent}) : super(parent: parent);
|
||||
|
||||
// TODO deps
|
||||
}
|
||||
|
||||
class $NAME$ScopeHolder extends ChildScopeHolder<$NAME$ScopeContainer, $PARENTNAME$> {
|
||||
$NAME$ScopeHolder($PARENTNAME$ parent) : super(parent);
|
||||
|
||||
@protected
|
||||
@override
|
||||
$NAME$ScopeContainer createContainer($PARENTNAME$ parent) =>
|
||||
$NAME$ScopeContainer(parent: parent);
|
||||
}
|
||||
```
|
||||
|
||||
## scope_data
|
||||
|
||||
```dart
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:yx_scope/yx_scope.dart';
|
||||
|
||||
class $NAME$ScopeContainer extends DataScopeContainer<$DATA$> {
|
||||
$NAME$ScopeContainer({required $DATA$ data}) : super(data: data);
|
||||
|
||||
// TODO deps
|
||||
}
|
||||
|
||||
class $NAME$ScopeHolder extends DataScopeHolder<$NAME$ScopeContainer, $DATA$> {
|
||||
@protected
|
||||
@override
|
||||
$NAME$ScopeContainer createContainer($DATA$ data) => $NAME$ScopeContainer(data: data);
|
||||
}
|
||||
```
|
||||
|
||||
## scope_child_data
|
||||
|
||||
```dart
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:yx_scope/yx_scope.dart';
|
||||
|
||||
class $NAME$ScopeContainer extends ChildDataScopeContainer<$PARENTNAME$, $DATA$> {
|
||||
$NAME$ScopeContainer({
|
||||
required $PARENTNAME$ parent,
|
||||
required $DATA$ data,
|
||||
}) : super(parent: parent, data: data);
|
||||
|
||||
// TODO deps
|
||||
}
|
||||
|
||||
class $NAME$ScopeHolder extends ChildDataScopeHolder<$NAME$ScopeContainer,
|
||||
$PARENTNAME$,
|
||||
$DATA$> {
|
||||
$NAME$ScopeHolder($PARENTNAME$ parent) : super(parent);
|
||||
|
||||
@protected
|
||||
@override
|
||||
$NAME$ScopeContainer createContainer($PARENTNAME$ parent,
|
||||
$DATA$ data,) =>
|
||||
$NAME$ScopeContainer(parent: parent, data: data);
|
||||
}
|
||||
```
|
||||
|
||||
## dep
|
||||
|
||||
```dart
|
||||
|
||||
late final $NAME$Dep = dep(() => $INSTANCE$);
|
||||
```
|
||||
|
||||
## dep_async
|
||||
|
||||
```dart
|
||||
|
||||
late final $NAME$Dep = asyncDep(() => $INSTANCE$);
|
||||
```
|
||||
|
||||
## dep_raw_async
|
||||
|
||||
```dart
|
||||
|
||||
late final $NAME$Dep = rawAsyncDep(
|
||||
() => $INSTANCE$,
|
||||
init: (value) async {
|
||||
// init
|
||||
},
|
||||
dispose: (value) async {
|
||||
// dispose
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
# VSCode
|
||||
|
||||
1. Go to File > Preferences > Configure Snippets.
|
||||
2. Select "New global snippets file" or "New snippets file for [your project]"
|
||||
3. Enter the name of the file
|
||||
4. Copy and paste the entire code below
|
||||
5. Save file
|
||||
|
||||
Now you can type keys from that json in any file of your project and apply the snippet by clicking
|
||||
Enter.
|
||||
|
||||
```
|
||||
{
|
||||
"scope": {
|
||||
"prefix": "scope",
|
||||
"body": [
|
||||
"import 'package:meta/meta.dart';",
|
||||
"import 'package:yx_scope/yx_scope.dart';",
|
||||
"",
|
||||
"class ${1:Name}ScopeContainer extends ScopeContainer {",
|
||||
" // TODO deps",
|
||||
"}",
|
||||
"",
|
||||
"class ${1:Name}ScopeHolder extends ScopeHolder<${1:Name}ScopeContainer> {",
|
||||
" @protected",
|
||||
" @override",
|
||||
" ${1:Name}ScopeContainer createContainer() => ${1:Name}ScopeContainer();",
|
||||
"}"
|
||||
],
|
||||
"description": "Template for scope"
|
||||
},
|
||||
"scope_child": {
|
||||
"prefix": "scope_child",
|
||||
"body": [
|
||||
"import 'package:meta/meta.dart';",
|
||||
"import 'package:yx_scope/yx_scope.dart';",
|
||||
"",
|
||||
"class ${1:Name}ScopeContainer extends ChildScopeContainer<${2:ParentName}> {",
|
||||
" ${1:Name}ScopeContainer({required ${2:ParentName} parent}) : super(parent: parent);",
|
||||
"",
|
||||
" // TODO deps",
|
||||
"}",
|
||||
"",
|
||||
"class ${1:Name}ScopeHolder",
|
||||
" extends ChildScopeHolder<${1:Name}ScopeContainer, ${2:ParentName}> {",
|
||||
" ${1:Name}ScopeHolder(${2:ParentName} parent) : super(parent);",
|
||||
" @protected",
|
||||
" @override",
|
||||
" ${1:Name}ScopeContainer createContainer(${2:ParentName} parent) =>",
|
||||
" ${1:Name}ScopeContainer(parent: parent);",
|
||||
"}"
|
||||
],
|
||||
"description": "Template for child scope"
|
||||
},
|
||||
"scope_data": {
|
||||
"prefix": "scope_data",
|
||||
"body": [
|
||||
"import 'package:meta/meta.dart';",
|
||||
"import 'package:yx_scope/yx_scope.dart';",
|
||||
"",
|
||||
"class ${1:Name}ScopeContainer extends DataScopeContainer<${2:Data}> {",
|
||||
" ${1:Name}ScopeContainer({required ${2:Data} data}) : super(data: data);",
|
||||
"",
|
||||
" // TODO deps",
|
||||
"}",
|
||||
"",
|
||||
"class ${1:Name}ScopeHolder extends DataScopeHolder<${1:Name}ScopeContainer, ${2:Data}> {",
|
||||
" @protected",
|
||||
" @override",
|
||||
" ${1:Name}ScopeContainer createContainer(${2:Data} data) => ${1:Name}ScopeContainer(data: data);",
|
||||
"}"
|
||||
],
|
||||
"description": "Template for scope data"
|
||||
},
|
||||
"scope_child_data": {
|
||||
"prefix": "scope_child_data",
|
||||
"body": [
|
||||
"import 'package:meta/meta.dart';",
|
||||
"import 'package:yx_scope/yx_scope.dart';",
|
||||
"",
|
||||
"class ${1:Name}ScopeContainer",
|
||||
" extends ChildDataScopeContainer<${2:ParentName}, ${3:Data}> {",
|
||||
" ${1:Name}ScopeContainer({",
|
||||
" required ${2:ParentName} parent,",
|
||||
" required ${3:Data} data,",
|
||||
" }) : super(parent: parent, data: data);",
|
||||
"",
|
||||
" // TODO deps",
|
||||
"}",
|
||||
"",
|
||||
"class ${1:Name}ScopeHolder extends ChildDataScopeHolder<${1:Name}ScopeContainer,",
|
||||
" ${2:ParentName}, ${3:Data}> {",
|
||||
" ${1:Name}ScopeHolder(${2:ParentName} parent) : super(parent);",
|
||||
" @protected",
|
||||
" @override",
|
||||
" ${1:Name}ScopeContainer createContainer(",
|
||||
" ${2:ParentName} parent,",
|
||||
" ${3:Data} data,",
|
||||
" ) =>",
|
||||
" ${1:Name}ScopeContainer(parent: parent, data: data);",
|
||||
"}"
|
||||
],
|
||||
"description": "Template for child data scope"
|
||||
},
|
||||
"dep": {
|
||||
"prefix": "dep",
|
||||
"body": [
|
||||
"late final ${1:Name}Dep = dep(() => ${2:Instance});"
|
||||
],
|
||||
"description": "Template for dependency"
|
||||
},
|
||||
"dep_async": {
|
||||
"prefix": "dep_async",
|
||||
"body": [
|
||||
"late final ${1:Name}Dep = asyncDep(() => ${2:Instance});"
|
||||
],
|
||||
"description": "Template for async dependency"
|
||||
},
|
||||
"raw_async_dep": {
|
||||
"prefix": "raw_async_dep",
|
||||
"body": [
|
||||
"late final ${1:Name}Dep = rawAsyncDep(",
|
||||
" () => ${2:Instance},",
|
||||
" init: (value) async {",
|
||||
" // init",
|
||||
" ${3:// Initialization logic here}",
|
||||
" },",
|
||||
" dispose: (value) async {",
|
||||
" // dispose",
|
||||
" ${4:// Disposal logic here}",
|
||||
" },",
|
||||
");"
|
||||
],
|
||||
"description": "Template for raw async dependency with init and dispose"
|
||||
}
|
||||
}
|
||||
```
|
||||
31
mnemo_cards_web_v2/packages/yx/city-services-pub/yx_scope/packages/yx_scope/example/.gitignore
vendored
Normal file
31
mnemo_cards_web_v2/packages/yx/city-services-pub/yx_scope/packages/yx_scope/example/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
migrate_working_dir/
|
||||
|
||||
# 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/
|
||||
build/
|
||||
|
||||
.arcignore
|
||||
|
|
@ -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: "4cf269e36de2573851eaef3c763994f8f9be494d"
|
||||
channel: "stable"
|
||||
|
||||
project_type: package
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
## 0.0.1
|
||||
|
||||
* TODO: Describe initial release.
|
||||
|
|
@ -0,0 +1 @@
|
|||
TODO: Add your license here.
|
||||
|
|
@ -0,0 +1 @@
|
|||
## An example for yx_scope
|
||||
|
|
@ -0,0 +1 @@
|
|||
include: package:lints/recommended.yaml
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
import 'package:yx_scope/yx_scope.dart';
|
||||
import './main.dart';
|
||||
|
||||
class AppScopeHolderWithListener extends ScopeHolder<AppScopeContainer> {
|
||||
static const _listener = AppListener();
|
||||
|
||||
AppScopeHolderWithListener()
|
||||
// ignore: deprecated_member_use
|
||||
: super(scopeListeners: [_listener], depListeners: [_listener]);
|
||||
|
||||
@override
|
||||
AppScopeContainer createContainer() => AppScopeContainer();
|
||||
}
|
||||
|
||||
// ignore: deprecated_member_use
|
||||
class AppListener implements ScopeListener, DepListener, AsyncDepListener {
|
||||
const AppListener();
|
||||
|
||||
static void _log(
|
||||
String message, [
|
||||
Object? exception,
|
||||
StackTrace? stackTrace,
|
||||
]) {
|
||||
print(message);
|
||||
if (exception != null) {
|
||||
print(exception);
|
||||
if (stackTrace != null) {
|
||||
print(stackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onScopeStartInitialize(ScopeId scope) =>
|
||||
_log('[$scope] -> onScopeStartInitialize');
|
||||
|
||||
@override
|
||||
void onScopeInitialized(ScopeId scope) =>
|
||||
_log('[$scope] -> onScopeInitialized');
|
||||
|
||||
@override
|
||||
void onScopeInitializeFailed(
|
||||
ScopeId scope,
|
||||
Object exception,
|
||||
StackTrace stackTrace,
|
||||
) =>
|
||||
_log('[$scope] -> onScopeInitializeFailed', exception, stackTrace);
|
||||
|
||||
@override
|
||||
void onScopeStartDispose(ScopeId scope) =>
|
||||
_log('[$scope] -> onScopeStartDispose');
|
||||
|
||||
@override
|
||||
void onScopeDisposed(ScopeId scope) => _log('[$scope] -> onScopeDisposed');
|
||||
|
||||
@override
|
||||
void onScopeDisposeDepFailed(
|
||||
ScopeId scope,
|
||||
DepId dep,
|
||||
Object exception,
|
||||
StackTrace stackTrace,
|
||||
) =>
|
||||
_log('[$scope] -> onScopeDisposeDepFailed', exception, stackTrace);
|
||||
|
||||
@override
|
||||
void onValueStartCreate(ScopeId scope, DepId dep) =>
|
||||
_log('[$scope.$dep] -> onValueStartCreate');
|
||||
|
||||
@override
|
||||
void onValueCreated(ScopeId scope, DepId dep, ValueMeta? valueMeta) =>
|
||||
_log('[$scope.$dep] -> onValueCreated');
|
||||
|
||||
@override
|
||||
void onValueCreateFailed(
|
||||
ScopeId scope,
|
||||
DepId dep,
|
||||
Object exception,
|
||||
StackTrace stackTrace,
|
||||
) =>
|
||||
_log('[$scope.$dep] -> onValueCreated', exception, stackTrace);
|
||||
|
||||
@override
|
||||
void onValueCleared(ScopeId scope, DepId dep, ValueMeta? valueMeta) =>
|
||||
_log('[$scope.$dep]($valueMeta) -> onValueCleared');
|
||||
|
||||
@override
|
||||
void onDepDisposeFailed(
|
||||
ScopeId scope, DepId dep, Object exception, StackTrace stackTrace) =>
|
||||
_log('[$scope.$dep] -> onDepDisposeFailed', exception, stackTrace);
|
||||
|
||||
@override
|
||||
void onDepDisposed(ScopeId scope, DepId dep) =>
|
||||
_log('[$scope.$dep] -> onDepDisposed');
|
||||
|
||||
@override
|
||||
void onDepInitializeFailed(
|
||||
ScopeId scope, DepId dep, Object exception, StackTrace stackTrace) =>
|
||||
_log('[$scope.$dep] -> onDepInitializeFailed', exception, stackTrace);
|
||||
|
||||
@override
|
||||
void onDepInitialized(ScopeId scope, DepId dep) =>
|
||||
_log('[$scope.$dep] -> onDepInitialized');
|
||||
|
||||
@override
|
||||
void onDepStartDispose(ScopeId scope, DepId dep) =>
|
||||
_log('[$scope.$dep] -> onDepStartDispose');
|
||||
|
||||
@override
|
||||
void onDepStartInitialize(ScopeId scope, DepId dep) =>
|
||||
_log('[$scope.$dep] -> onDepStartInitialize');
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import 'app_listener.dart';
|
||||
|
||||
/// This file will be deleted in the next major version
|
||||
/// when Listeners will be completely removed
|
||||
void main() async {
|
||||
final appScopeHolderWithListener = AppScopeHolderWithListener();
|
||||
|
||||
await appScopeHolderWithListener.create();
|
||||
|
||||
print(appScopeHolderWithListener.scope?.routerDelegateDep.get);
|
||||
|
||||
await appScopeHolderWithListener.drop();
|
||||
|
||||
print(appScopeHolderWithListener.scope?.routerDelegateDep.get);
|
||||
}
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
import 'package:yx_scope/yx_scope.dart';
|
||||
|
||||
void main() async {
|
||||
final appScopeHolder = AppScopeHolder();
|
||||
|
||||
await appScopeHolder.create();
|
||||
|
||||
print(appScopeHolder.scope?.routerDelegateDep.get);
|
||||
|
||||
await appScopeHolder.drop();
|
||||
|
||||
print(appScopeHolder.scope?.routerDelegateDep.get);
|
||||
}
|
||||
|
||||
class AppRouterDelegate {}
|
||||
|
||||
class AppStateObserver {
|
||||
final AppRouterDelegate appRouteDelegate;
|
||||
|
||||
AppStateObserver(this.appRouteDelegate);
|
||||
}
|
||||
|
||||
class AppScopeContainer extends ScopeContainer {
|
||||
late final routerDelegateDep = dep(() => AppRouterDelegate());
|
||||
|
||||
late final appStateObserverDep = dep(
|
||||
() => AppStateObserver(
|
||||
routerDelegateDep.get,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class AppScopeHolder extends ScopeHolder<AppScopeContainer> {
|
||||
static const _observer = AppObserver();
|
||||
|
||||
AppScopeHolder()
|
||||
: super(scopeObservers: [_observer], depObservers: [_observer]);
|
||||
|
||||
@override
|
||||
AppScopeContainer createContainer() => AppScopeContainer();
|
||||
}
|
||||
|
||||
class AppObserver implements ScopeObserver, DepObserver, AsyncDepObserver {
|
||||
const AppObserver();
|
||||
|
||||
static void _log(
|
||||
String message, [
|
||||
Object? exception,
|
||||
StackTrace? stackTrace,
|
||||
]) {
|
||||
print(message);
|
||||
if (exception != null) {
|
||||
print(exception);
|
||||
if (stackTrace != null) {
|
||||
print(stackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onScopeStartInitialize(ScopeId scope) =>
|
||||
_log('[$scope] -> onScopeStartInitialize');
|
||||
|
||||
@override
|
||||
void onScopeInitialized(ScopeId scope) =>
|
||||
_log('[$scope] -> onScopeInitialized');
|
||||
|
||||
@override
|
||||
void onScopeInitializeFailed(
|
||||
ScopeId scope,
|
||||
Object exception,
|
||||
StackTrace stackTrace,
|
||||
) =>
|
||||
_log('[$scope] -> onScopeInitializeFailed', exception, stackTrace);
|
||||
|
||||
@override
|
||||
void onScopeStartDispose(ScopeId scope) =>
|
||||
_log('[$scope] -> onScopeStartDispose');
|
||||
|
||||
@override
|
||||
void onScopeDisposed(ScopeId scope) => _log('[$scope] -> onScopeDisposed');
|
||||
|
||||
@override
|
||||
void onScopeDisposeDepFailed(
|
||||
ScopeId scope,
|
||||
DepId dep,
|
||||
Object exception,
|
||||
StackTrace stackTrace,
|
||||
) =>
|
||||
_log('[$scope] -> onScopeDisposeDepFailed', exception, stackTrace);
|
||||
|
||||
@override
|
||||
void onValueStartCreate(ScopeId scope, DepId dep) =>
|
||||
_log('[$scope.$dep] -> onValueStartCreate');
|
||||
|
||||
@override
|
||||
void onValueCreated(ScopeId scope, DepId dep, ValueMeta? valueMeta) =>
|
||||
_log('[$scope.$dep] -> onValueCreated');
|
||||
|
||||
@override
|
||||
void onValueCreateFailed(
|
||||
ScopeId scope,
|
||||
DepId dep,
|
||||
Object exception,
|
||||
StackTrace stackTrace,
|
||||
) =>
|
||||
_log('[$scope.$dep] -> onValueCreated', exception, stackTrace);
|
||||
|
||||
@override
|
||||
void onValueCleared(ScopeId scope, DepId dep, ValueMeta? valueMeta) =>
|
||||
_log('[$scope.$dep]($valueMeta) -> onValueCleared');
|
||||
|
||||
@override
|
||||
void onDepDisposeFailed(
|
||||
ScopeId scope, DepId dep, Object exception, StackTrace stackTrace) =>
|
||||
_log('[$scope.$dep] -> onDepDisposeFailed', exception, stackTrace);
|
||||
|
||||
@override
|
||||
void onDepDisposed(ScopeId scope, DepId dep) =>
|
||||
_log('[$scope.$dep] -> onDepDisposed');
|
||||
|
||||
@override
|
||||
void onDepInitializeFailed(
|
||||
ScopeId scope, DepId dep, Object exception, StackTrace stackTrace) =>
|
||||
_log('[$scope.$dep] -> onDepInitializeFailed', exception, stackTrace);
|
||||
|
||||
@override
|
||||
void onDepInitialized(ScopeId scope, DepId dep) =>
|
||||
_log('[$scope.$dep] -> onDepInitialized');
|
||||
|
||||
@override
|
||||
void onDepStartDispose(ScopeId scope, DepId dep) =>
|
||||
_log('[$scope.$dep] -> onDepStartDispose');
|
||||
|
||||
@override
|
||||
void onDepStartInitialize(ScopeId scope, DepId dep) =>
|
||||
_log('[$scope.$dep] -> onDepStartInitialize');
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
name: yx_scope_example
|
||||
description: "An example for yx_scope"
|
||||
version: 1.0.0
|
||||
publish_to: 'none'
|
||||
|
||||
environment:
|
||||
sdk: ">=2.19.0 <4.0.0"
|
||||
|
||||
dependencies:
|
||||
yx_scope:
|
||||
path: ..
|
||||
|
||||
dev_dependencies:
|
||||
lints: ^2.0.1
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
export 'src/monitoring/raw_observers.dart';
|
||||
export 'src/base_scope_container.dart' show CustomDep, CustomAsyncDep;
|
||||
|
|
@ -0,0 +1,288 @@
|
|||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import 'core/async_lifecycle.dart';
|
||||
import 'core/scope_exception.dart';
|
||||
import 'core/scope_state.dart';
|
||||
import 'monitoring/observers.dart';
|
||||
import 'monitoring/models/dep_id.dart';
|
||||
import 'monitoring/models/scope_id.dart';
|
||||
import 'monitoring/models/value_meta.dart';
|
||||
import 'monitoring/raw_observers.dart';
|
||||
import 'monitoring/scope_observatory_internal.dart';
|
||||
import 'scope_container.dart';
|
||||
import 'scope_state_streamable.dart';
|
||||
|
||||
part 'core/scope_state_holder.dart';
|
||||
|
||||
part 'core_scope_holder.dart';
|
||||
|
||||
part 'custom_dep.dart';
|
||||
|
||||
part 'dep.dart';
|
||||
|
||||
part 'monitoring/observers_internal.dart';
|
||||
|
||||
part 'monitoring/models/scope_meta.dart';
|
||||
|
||||
part 'scope_holder.dart';
|
||||
|
||||
part 'scope_module.dart';
|
||||
|
||||
part 'test_utils/scope_container_test_utils.dart';
|
||||
|
||||
part 'test_utils/scope_state_test_utils.dart';
|
||||
|
||||
typedef _AsyncVoidCallback = Future<void> Function();
|
||||
|
||||
typedef _AsyncVoidCallbackRemove = void Function();
|
||||
|
||||
typedef AsyncDepCallback<Value> = Future<void> Function(Value dep);
|
||||
|
||||
/// {@template base_scope_container}
|
||||
/// A container and a description of a scope.
|
||||
///
|
||||
/// A [BaseScopeContainer] is responsible for managing the lifecycle
|
||||
/// of its associated dependencies.
|
||||
///
|
||||
/// It provides a convenient way to group
|
||||
/// related dependencies together and initialize them at the same time.
|
||||
///
|
||||
/// To create a [ScopeContainer]:
|
||||
/// 1. Declare a subclass
|
||||
/// 2. Declare dependencies: late final yourEntityDep = [dep] (or [asyncDep])
|
||||
/// 3. Override [initializeQueue] getter to specify which dependencies
|
||||
/// should be initialized when the [ScopeContainer] is initialized.
|
||||
///
|
||||
/// An example:
|
||||
///
|
||||
/// class SomeScopeContainer extends ScopeContainer {
|
||||
/// late final appManagerDep = dep(() => AppManager());
|
||||
///
|
||||
/// late final reporterDep = dep(() => Reporter());
|
||||
///
|
||||
/// late final navigationDep = dep(() => Navigation());
|
||||
/// }
|
||||
/// {@endtemplate}
|
||||
abstract class BaseScopeContainer extends Scope {
|
||||
final _container = <Dep>[];
|
||||
|
||||
final String? _name;
|
||||
late final ScopeId _id;
|
||||
|
||||
late final DepObserverInternal _depObserver;
|
||||
late final AsyncDepObserverInternal _asyncDepObserver;
|
||||
|
||||
BaseScopeContainer({String? name})
|
||||
: _name = name,
|
||||
super._() {
|
||||
_id = ScopeId(runtimeType, hashCode, _name);
|
||||
_depObserver = DepObserverInternal(this);
|
||||
_asyncDepObserver = AsyncDepObserverInternal(this);
|
||||
}
|
||||
|
||||
/// A queue of the initialization for [AsyncDep].
|
||||
/// The order of the execution is the following:
|
||||
/// [Dep]s inside each Set are executed in parallel in random order —
|
||||
/// if dependencies do not depend from each other then place them in Set.
|
||||
/// Sets are executed in the order of the List — from 0 to the next and so on.
|
||||
/// If some dependencies must be executed in specific order, consider placing
|
||||
/// them in separate Sets and order relatively.
|
||||
@protected
|
||||
List<Set<AsyncDep>> get initializeQueue => [];
|
||||
|
||||
/// The only correct way to declare a [Dep] inside [BaseScopeContainer].
|
||||
/// Returns a factory for your [Value].
|
||||
///
|
||||
/// Here is an example:
|
||||
/// ```
|
||||
/// late final someManagerDep = dep(() => SomeManager());
|
||||
/// ```
|
||||
/// [Dep] must be a late final field inside [BaseScopeContainer]. No methods!
|
||||
/// Add suffix 'Dep' to the name of your dependency — it explicitly says in the code
|
||||
/// that you are working with [Dep] and not the entity (ex. SomeManager) itself.
|
||||
///
|
||||
/// If your entity depends on the other entity, use this approach:
|
||||
/// ```
|
||||
/// late final dependentManagerDep = dep(() => DependentManager(someManagerDep.get));
|
||||
/// ```
|
||||
/// In this example DependentManager depends on SomeManager. So we access
|
||||
/// SomeManager via Dep<SomeManager> and pass an actual instance
|
||||
/// of the SomeManager inside DependentManager.
|
||||
///
|
||||
/// [name] parameter can be ignored unless you want to see distinguish
|
||||
/// dependencies with the same [Type] in the same or in different scopes.
|
||||
@nonVirtual
|
||||
@protected
|
||||
Dep<Value> dep<Value>(
|
||||
DepBuilder<Value> builder, {
|
||||
String? name,
|
||||
}) =>
|
||||
Dep._(this, builder, name: name, observer: _depObserver);
|
||||
|
||||
/// Exactly the same as [BaseScopeContainer.dep] but you only allowed
|
||||
/// to declare [AsyncLifecycle] dependencies using this method.
|
||||
///
|
||||
/// [name] parameter can be ignored unless you want to see distinguish
|
||||
/// dependencies with the same [Type] in the same or in different scopes.
|
||||
@nonVirtual
|
||||
@protected
|
||||
AsyncDep<Value> asyncDep<Value extends AsyncLifecycle>(
|
||||
DepBuilder<Value> builder, {
|
||||
String? name,
|
||||
}) =>
|
||||
rawAsyncDep(
|
||||
builder,
|
||||
init: (value) => value.init(),
|
||||
dispose: (value) => value.dispose(),
|
||||
name: name,
|
||||
);
|
||||
|
||||
/// Exactly the same as [BaseScopeContainer.asyncDep] but you must
|
||||
/// declare init and dispose functions.
|
||||
///
|
||||
/// It is useful if you don't want to implement [AsyncLifecycle] interface,
|
||||
/// but you still have async init/dispose process in your entity.
|
||||
///
|
||||
/// [name] parameter can be ignored unless you want to see distinguish
|
||||
/// dependencies with the same [Type] in the same or in different scopes.
|
||||
@nonVirtual
|
||||
@protected
|
||||
AsyncDep<Value> rawAsyncDep<Value>(
|
||||
DepBuilder<Value> builder, {
|
||||
required AsyncDepCallback<Value> init,
|
||||
required AsyncDepCallback<Value> dispose,
|
||||
String? name,
|
||||
}) =>
|
||||
AsyncDep._(
|
||||
this,
|
||||
builder,
|
||||
init: init,
|
||||
dispose: dispose,
|
||||
name: name,
|
||||
observer: _asyncDepObserver,
|
||||
);
|
||||
|
||||
void _registerDep(Dep dep) => _container.add(dep);
|
||||
|
||||
void _unregister() {
|
||||
for (final dep in _container.reversed) {
|
||||
dep._unregister();
|
||||
}
|
||||
_container.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// Mixin for [BaseScopeContainer].
|
||||
///
|
||||
/// {@template child_scope_container}
|
||||
/// Helps to have a parent in a scope.
|
||||
/// If the scope has a parent you can access
|
||||
/// parent dependencies within a current scope.
|
||||
/// {@endtemplate}
|
||||
mixin ChildScopeContainerMixin<Parent extends Scope> on BaseScopeContainer {
|
||||
Parent? _parent;
|
||||
|
||||
/// Must not be used anyone except for the child with this mixin.
|
||||
/// Should only be used inside constructor, see [ChildScopeContainer].
|
||||
@protected
|
||||
set parent(Parent parent) => _parent = parent;
|
||||
|
||||
/// Access parent of the current [BaseScopeContainer].
|
||||
Parent get parent {
|
||||
final p = _parent;
|
||||
if (p == null) {
|
||||
throw ScopeException(
|
||||
'$runtimeType: You must set parent in your Scope constructor',
|
||||
);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
/// Mixin for [BaseScopeContainer].
|
||||
/// {@template data_scope_container}
|
||||
/// Helps to have a initial data in a scope.
|
||||
/// If the scope has data, you can access
|
||||
/// it as a non-nullable value within current scope.
|
||||
/// {@endtemplate}
|
||||
mixin DataScopeContainerMixin<Data extends Object> on BaseScopeContainer {
|
||||
Data? _data;
|
||||
|
||||
/// Must not be used anyone except for the child with this mixin.
|
||||
/// Should only be used inside constructor, see [DataScopeContainer].
|
||||
@protected
|
||||
set data(Data data) => _data = data;
|
||||
|
||||
/// Access data of the current [BaseScopeContainer].
|
||||
Data get data {
|
||||
final data = _data;
|
||||
if (data == null) {
|
||||
throw ScopeException(
|
||||
'$runtimeType: You must set data in your Scope constructor',
|
||||
);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
/// This is an abstract interface for a custom developer interface
|
||||
/// that is considered to be implemented by their [BaseScopeContainer].
|
||||
///
|
||||
/// For example, you have a [ScopeContainer]:
|
||||
///
|
||||
/// class SomeScopeContainer extends ScopeContainer {
|
||||
/// late final appManagerDep = dep(() => AppManager());
|
||||
///
|
||||
/// late final navigationDep = dep(() => Navigation());
|
||||
///
|
||||
/// late final reporterDep = dep(() => Reporter());
|
||||
/// }
|
||||
///
|
||||
/// But you don't want to share actual deps with your consumers or
|
||||
/// you don't want them to know anything about your SomeScopeContainer implementation.
|
||||
///
|
||||
/// Then you create an interface:
|
||||
///
|
||||
/// abstract class SomeScope implements BaseScope {
|
||||
/// AppManager get appManager;
|
||||
///
|
||||
/// NavigationManager get navigationManager;
|
||||
/// }
|
||||
///
|
||||
/// and implement it by your SomeScopeContainer:
|
||||
///
|
||||
/// class SomeScopeContainer extends ScopeContainer implements SomeScope {
|
||||
/// @override
|
||||
/// AppManager get appManager => appManagerDep.get;
|
||||
///
|
||||
/// @override
|
||||
/// NavigationManager get navigationManager => navigationDep.get;
|
||||
///
|
||||
/// late final appManagerDep = dep(() => AppManager());
|
||||
///
|
||||
/// late final navigationDep = dep(() => Navigation());
|
||||
///
|
||||
/// late final reporterDep = dep(() => Reporter());
|
||||
/// }
|
||||
///
|
||||
/// It helps you in two ways:
|
||||
/// 1. You can now use [BaseScopeHolder] (or Child/Data/ChildData-ScopeHolder)
|
||||
/// and pass your interface as a first generic parameter for your ScopeHolder.
|
||||
/// It narrows access to your deps giving only the contract that is declared by the interface.
|
||||
/// 2. You can pass you interface as a [parent] to your [ChildScopeHolder] instead of
|
||||
/// actual [BaseScopeContainer] implementation. It also narrows access to deps from the parent.
|
||||
abstract class Scope {
|
||||
final _disposeListeners = <_AsyncVoidCallback>{};
|
||||
|
||||
_AsyncVoidCallbackRemove _listenDispose(_AsyncVoidCallback listener) {
|
||||
_disposeListeners.add(listener);
|
||||
return () {
|
||||
_disposeListeners.remove(listener);
|
||||
};
|
||||
}
|
||||
|
||||
Scope._();
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
/// An interface for entities that must be initialized and disposed.
|
||||
/// You can use this interface both for asynchronous and synchronous initialization.
|
||||
///
|
||||
/// If your entity implements this interface and it's created in [ScopeContainer],
|
||||
/// you have to use [AsyncDep] for instance
|
||||
/// and pass this dep into [ScopeContainer.initializeQueue].
|
||||
abstract class AsyncLifecycle {
|
||||
/// When you use [AsyncLifecycle] in [ScopeContainer],
|
||||
/// this method will be called during initialization of this [ScopeContainer].
|
||||
Future<void> init();
|
||||
|
||||
/// When you use [AsyncLifecycle] in [ScopeContainer],
|
||||
/// this method will be called during dispose of the [ScopeContainer].
|
||||
Future<void> dispose();
|
||||
|
||||
/// AsyncLifecycle can be used only as an interface via 'implements'
|
||||
const AsyncLifecycle._();
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
// This exception must be throw in cases when
|
||||
// a consumer of this package uses it
|
||||
// in some inappropriate way.
|
||||
class ScopeException implements Exception {
|
||||
final String message;
|
||||
|
||||
const ScopeException(this.message);
|
||||
|
||||
@override
|
||||
String toString() => 'ScopeException: $message';
|
||||
}
|
||||
|
||||
// This error must be throw in cases when
|
||||
// the problem happens because of
|
||||
// internal code problems of this package.
|
||||
class ScopeError extends ScopeException {
|
||||
const ScopeError(String message) : super(message);
|
||||
|
||||
@override
|
||||
String toString() => 'ScopeError: $message';
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
abstract class ScopeState<Scope> {
|
||||
ScopeState._();
|
||||
|
||||
factory ScopeState.none() = ScopeStateNone;
|
||||
factory ScopeState.initializing() = ScopeStateInitializing;
|
||||
factory ScopeState.available({required Scope scope}) = ScopeStateAvailable;
|
||||
factory ScopeState.disposing() = ScopeStateDisposing;
|
||||
|
||||
bool get none => this is ScopeStateNone;
|
||||
|
||||
bool get initializing => this is ScopeStateInitializing;
|
||||
|
||||
bool get available => this is ScopeStateAvailable;
|
||||
|
||||
bool get disposing => this is ScopeStateDisposing;
|
||||
}
|
||||
|
||||
class ScopeStateNone<Scope> extends ScopeState<Scope> {
|
||||
ScopeStateNone() : super._();
|
||||
|
||||
@override
|
||||
String toString() => 'ScopeState<$Scope>.none';
|
||||
}
|
||||
|
||||
class ScopeStateInitializing<Scope> extends ScopeState<Scope> {
|
||||
ScopeStateInitializing() : super._();
|
||||
|
||||
@override
|
||||
String toString() => 'ScopeState<$Scope>.initializing';
|
||||
}
|
||||
|
||||
class ScopeStateAvailable<Scope> extends ScopeState<Scope> {
|
||||
final Scope scope;
|
||||
|
||||
ScopeStateAvailable({required this.scope}) : super._();
|
||||
|
||||
@override
|
||||
String toString() => 'ScopeState<$Scope>.available';
|
||||
}
|
||||
|
||||
class ScopeStateDisposing<Scope> extends ScopeState<Scope> {
|
||||
ScopeStateDisposing() : super._();
|
||||
|
||||
@override
|
||||
String toString() => 'ScopeState<$Scope>.disposing';
|
||||
}
|
||||
|
|
@ -0,0 +1,181 @@
|
|||
part of '../base_scope_container.dart';
|
||||
|
||||
typedef StateListener<S> = void Function(S? scope);
|
||||
|
||||
typedef ScopeStateListener<S> = void Function(ScopeState<S> state);
|
||||
|
||||
typedef RemoveStateListener = void Function();
|
||||
|
||||
class ScopeStateHolder<Scope> {
|
||||
final _listeners = LinkedList<Entry<Scope>>();
|
||||
ScopeState<Scope> _state;
|
||||
|
||||
bool _debugCanAddListeners = true;
|
||||
|
||||
ScopeStateHolder(this._state);
|
||||
|
||||
Scope? get scope {
|
||||
final state = this.state;
|
||||
if (state is ScopeStateAvailable<Scope>) {
|
||||
return state.scope;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
ScopeState<Scope> get state => _state;
|
||||
|
||||
void _updateState(ScopeState<Scope> state) {
|
||||
_state = state;
|
||||
|
||||
final errors = <Object>[];
|
||||
final stackTraces = <StackTrace>[];
|
||||
|
||||
final listeners = [..._listeners];
|
||||
for (final listener in listeners) {
|
||||
try {
|
||||
listener.listener(state);
|
||||
} on Object catch (e, s) {
|
||||
errors.add(e);
|
||||
stackTraces.add(s);
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.isNotEmpty) {
|
||||
throw NotifyListenerError._(errors, stackTraces);
|
||||
}
|
||||
}
|
||||
|
||||
/// Subscribes to the state.
|
||||
///
|
||||
/// The [listener] callback will be called immediately on addition and
|
||||
/// synchronously whenever [state] changes.
|
||||
///
|
||||
/// Note: This method only calls the callback when the state is [ScopeStateAvailable]
|
||||
/// (passing the scope object) or when the scope is [ScopeStateNone] (passing null).
|
||||
/// It doesn't trigger for [ScopeStateInitializing] or [ScopeStateDisposing] states.
|
||||
///
|
||||
/// Set [emitImmediately] to true if you want to an immediate execution
|
||||
/// of the [listener] with the current state.
|
||||
///
|
||||
/// To remove this [listener], call the function returned by [listen].
|
||||
///
|
||||
/// Listeners cannot add other listeners.
|
||||
/// Adding and removing listeners has a constant time-complexity.
|
||||
RemoveStateListener listen(
|
||||
StateListener<Scope> listener, {
|
||||
bool emitImmediately = false,
|
||||
}) =>
|
||||
_listen(
|
||||
(state) {
|
||||
if (state is ScopeStateAvailable<Scope>) {
|
||||
listener(state.scope);
|
||||
} else if (state is ScopeStateNone) {
|
||||
listener(null);
|
||||
}
|
||||
},
|
||||
emitImmediately: emitImmediately,
|
||||
);
|
||||
|
||||
/// Subscribes to the state.
|
||||
///
|
||||
/// The [listener] callback will be called immediately on addition and
|
||||
/// synchronously whenever [state] changes.
|
||||
///
|
||||
/// Note: This method emits on every [ScopeState] change.
|
||||
///
|
||||
/// Set [emitImmediately] to true if you want to an immediate execution
|
||||
/// of the [listener] with the current state.
|
||||
///
|
||||
/// To remove this [listener], call the function returned by [listen].
|
||||
///
|
||||
/// Listeners cannot add other listeners.
|
||||
/// Adding and removing listeners has a constant time-complexity.
|
||||
RemoveStateListener listenState(
|
||||
ScopeStateListener<Scope> listener, {
|
||||
bool emitImmediately = false,
|
||||
}) =>
|
||||
_listen(
|
||||
(state) => listener(state),
|
||||
emitImmediately: emitImmediately,
|
||||
);
|
||||
|
||||
RemoveStateListener _listen(
|
||||
void Function(ScopeState<Scope> state) listener, {
|
||||
bool emitImmediately = false,
|
||||
}) {
|
||||
assert(() {
|
||||
if (!_debugCanAddListeners) {
|
||||
throw ConcurrentModificationError();
|
||||
}
|
||||
return true;
|
||||
}(), '');
|
||||
final listenerEntry = Entry<Scope>(listener);
|
||||
if (emitImmediately) {
|
||||
assert(_debugSetCanAddListeners(false), '');
|
||||
try {
|
||||
// Intentionally unsafe call of the listener before adding to the [_listeners]
|
||||
// so that if there is an exception — we throw it back to consumer
|
||||
// with an original stacktrace without adding to the [_listeners].
|
||||
listener(state);
|
||||
} on Object catch (_) {
|
||||
rethrow;
|
||||
} finally {
|
||||
assert(_debugSetCanAddListeners(true), '');
|
||||
}
|
||||
}
|
||||
_listeners.add(listenerEntry);
|
||||
|
||||
return () {
|
||||
if (listenerEntry.list != null) {
|
||||
listenerEntry.unlink();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
bool _debugSetCanAddListeners(bool value) {
|
||||
assert(() {
|
||||
_debugCanAddListeners = value;
|
||||
return true;
|
||||
}(), '');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
class Entry<T> extends LinkedListEntry<Entry<T>> {
|
||||
Entry(this.listener);
|
||||
|
||||
final ScopeStateListener<T> listener;
|
||||
}
|
||||
|
||||
/// An error thrown when tried to update the state of a [ScopeStateHolder],
|
||||
/// but some of the listeners threw an exception.
|
||||
class NotifyListenerError extends Error {
|
||||
NotifyListenerError._(
|
||||
this.errors,
|
||||
this.stackTraces,
|
||||
) : assert(
|
||||
errors.length == stackTraces.length,
|
||||
'errors and stackTraces must match',
|
||||
);
|
||||
|
||||
final List<Object> errors;
|
||||
final List<StackTrace?> stackTraces;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
final buffer = StringBuffer();
|
||||
|
||||
for (var i = 0; i < errors.length; i++) {
|
||||
final error = errors[i];
|
||||
final stackTrace = stackTraces[i];
|
||||
|
||||
buffer
|
||||
..writeln(error)
|
||||
..writeln(stackTrace);
|
||||
}
|
||||
|
||||
return 'Some of listeners threw exception when updating the state. '
|
||||
'The exceptions thrown are:\n$buffer';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,384 @@
|
|||
part of 'base_scope_container.dart';
|
||||
|
||||
/// DO NOT USE THIS CLASS MANUALLY!
|
||||
/// Use [ScopeHolder], [ChildScopeHolder],
|
||||
/// [DataScopeHolder] or [ChildDataScopeHolder] instead.
|
||||
///
|
||||
/// Holder contains the state of a [BaseScopeContainer] — null or the scope itself.
|
||||
/// This is the core entity that provides access to the [BaseScopeContainer].
|
||||
abstract class CoreScopeHolder<Scope, Container extends BaseScopeContainer>
|
||||
extends ScopeStateHolder<Scope> with ScopeStateStreamable<Scope> {
|
||||
final ScopeObserverInternal _scopeObserverInternal;
|
||||
final List<DepObserver>? _depObservers;
|
||||
final List<AsyncDepObserver>? _asyncDepObservers;
|
||||
|
||||
Completer? _waitLifecycleCompleter;
|
||||
|
||||
CoreScopeHolder({
|
||||
List<ScopeObserver>? scopeObservers,
|
||||
List<DepObserver>? depObservers,
|
||||
List<AsyncDepObserver>? asyncDepObservers,
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
@Deprecated('Use scopeObservers instead')
|
||||
List<ScopeListener>? scopeListeners,
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
@Deprecated('Use depObservers instead') List<DepListener>? depListeners,
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
@Deprecated('Use asyncDepObservers instead')
|
||||
List<AsyncDepListener>? asyncDepListeners,
|
||||
}) : assert(!(scopeListeners != null && scopeObservers != null),
|
||||
'Both scopeObservers and scopeListeners passed as arguments to ScopeHolder. Consider using only scopeObservers'),
|
||||
assert(!(depListeners != null && depObservers != null),
|
||||
'Both depObservers and depListeners passed as arguments to ScopeHolder. Consider using only depObservers'),
|
||||
assert(!(asyncDepListeners != null && asyncDepObservers != null),
|
||||
'Both asyncDepObservers and asyncDepListeners passed as arguments to ScopeHolder. Consider using only asyncDepObservers'),
|
||||
_scopeObserverInternal =
|
||||
ScopeObserverInternal(scopeObservers ?? scopeListeners),
|
||||
_depObservers = depObservers ?? depListeners,
|
||||
_asyncDepObservers = asyncDepObservers ?? asyncDepListeners,
|
||||
super(ScopeState.none());
|
||||
|
||||
/// Initialize scope. [Scope] becomes available and everyone can
|
||||
/// start working with it via [BaseScopeHolder].
|
||||
///
|
||||
/// Throws [ScopeException] in following cases:
|
||||
/// 1. [Container] does not implement [Scope]
|
||||
/// 2. [init] is called when the previous [init] is still running
|
||||
/// 3. [init] is called when [Scope] is already [ScopeState.available]
|
||||
/// 4. [init] is called when [dispose] is now running
|
||||
/// and there is already another [init] is waiting for [dispose] to complete
|
||||
/// 5. [AsyncDep] from another [BaseScopeContainer] initialized in this [BaseScopeContainer.initializeQueue].
|
||||
///
|
||||
@protected
|
||||
@mustCallSuper
|
||||
Future<void> init(Container scope) async {
|
||||
if (scope is! Scope) {
|
||||
throw ScopeException('You must implement $Scope for your $Container');
|
||||
}
|
||||
|
||||
if (state.initializing) {
|
||||
throw ScopeException(
|
||||
'You are trying to initialize $Container that is initializing right now. '
|
||||
'Given instances of the $Container might be different, '
|
||||
'so do not call `create` sequentially without `drops`',
|
||||
);
|
||||
}
|
||||
|
||||
if (state.available) {
|
||||
throw ScopeException(
|
||||
'You are trying to initialize $Container that has been already initialized'
|
||||
'Given instances of the $Container might be different, '
|
||||
'so do not call `create` sequentially without `drops`',
|
||||
);
|
||||
}
|
||||
|
||||
if (state.disposing) {
|
||||
Logger.warning(
|
||||
'$Container calls init method while disposing. '
|
||||
'This is a weird situation and can lead to unexpected behaviour.',
|
||||
);
|
||||
final currentCompleter = _waitLifecycleCompleter;
|
||||
if (currentCompleter != null) {
|
||||
throw ScopeException(
|
||||
'Scope is already waiting for dispose in order to be recreated again. '
|
||||
'Probably you have called `create` method without await a few times in a row.',
|
||||
);
|
||||
}
|
||||
final completer = Completer.sync();
|
||||
_waitLifecycleCompleter = completer;
|
||||
final removeListener = listenState((state) {
|
||||
if (state.none) {
|
||||
completer.complete();
|
||||
} else {
|
||||
completer.completeError(
|
||||
ScopeError(
|
||||
'Unexpected state ($state) after ${ScopeState.disposing},'
|
||||
' must be ${ScopeState.none}',
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
try {
|
||||
await completer.future;
|
||||
} on Object catch (e, s) {
|
||||
Error.throwWithStackTrace(
|
||||
ScopeError(
|
||||
'$e\n'
|
||||
'Unexpected exception when were waiting for dispose during initialization. '
|
||||
'This is definitely an error in the library,'
|
||||
' please contact an owner, if you see this message.',
|
||||
),
|
||||
s,
|
||||
);
|
||||
} finally {
|
||||
_waitLifecycleCompleter = null;
|
||||
removeListener();
|
||||
Logger.debug(
|
||||
'Wait for scope dispose has completed, state=$state',
|
||||
);
|
||||
if (!state.none) {
|
||||
throw ScopeError(
|
||||
'Scope initialization waited for dispose of the previous scope state, '
|
||||
'it\'s expected to be ${ScopeState.none}, '
|
||||
'but it appeared to be $state.'
|
||||
'This is definitely an error in the library,'
|
||||
' please contact an owner, if you see this message.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_prepareObservers(scope);
|
||||
|
||||
_scopeObserverInternal.onScopeStartInitialize(scope);
|
||||
|
||||
_initializing();
|
||||
|
||||
// Cache already initialized dependencies in order to
|
||||
// dispose only initialized ones in case of an exception
|
||||
final initialized = <Set<AsyncDep>>[];
|
||||
final queue = scope.initializeQueue;
|
||||
try {
|
||||
for (var i = 0; i < queue.length; i++) {
|
||||
initialized.add({});
|
||||
final depSet = queue[i];
|
||||
await Future.wait(
|
||||
depSet.map(
|
||||
(dep) {
|
||||
final scopeType = Container;
|
||||
final depType = dep.runtimeType;
|
||||
if (dep._scope != scope) {
|
||||
throw ScopeException(
|
||||
'You are initializing async dep $depType '
|
||||
'within ${scope.runtimeType}#${scope.hashCode}, '
|
||||
'but the dep declared in ${dep._scope.runtimeType}#${dep._scope.hashCode}',
|
||||
);
|
||||
}
|
||||
Logger.debug('($scopeType) Initializing: $depType');
|
||||
return dep._init().then(
|
||||
(_) {
|
||||
initialized[i].add(dep);
|
||||
Logger.debug('($scopeType) Initialized: $depType');
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
} on Object catch (e, s) {
|
||||
_scopeObserverInternal.onScopeInitializeFailed(scope, e, s);
|
||||
|
||||
await _drop(initializedScope: scope, initializedDeps: initialized);
|
||||
rethrow;
|
||||
}
|
||||
_available(scope as Scope);
|
||||
_scopeObserverInternal.onScopeInitialized(scope);
|
||||
}
|
||||
|
||||
/// Dispose scope. [Scope] becomes unavailable.
|
||||
///
|
||||
/// In debug throws [AssertionError] in following cases:
|
||||
/// 1. [init] is called when the previous [dispose] is still running
|
||||
/// 2. [init] is called when [Scope] is already [ScopeState.none]
|
||||
///
|
||||
/// Throws [ScopeException] in following cases:
|
||||
/// 1. [init] is called when [init] is now running
|
||||
/// and there is already another [dispose] is waiting for [init] to complete
|
||||
///
|
||||
@mustCallSuper
|
||||
Future<void> drop() => _drop();
|
||||
|
||||
Future<void> _drop({
|
||||
Container? initializedScope,
|
||||
List<Set<AsyncDep>>? initializedDeps,
|
||||
}) async {
|
||||
if (!((initializedScope == null && initializedDeps == null) ||
|
||||
(initializedScope != null && initializedDeps != null))) {
|
||||
throw const ScopeError(
|
||||
'[Internal] _drop method must be called with either '
|
||||
'both nullable args (user manual drop) or '
|
||||
'both non-nullable args (drop during initialization)',
|
||||
);
|
||||
}
|
||||
|
||||
if (state.disposing) {
|
||||
assert(
|
||||
false,
|
||||
'You are trying to dispose $Container that is disposing right now',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.none) {
|
||||
assert(
|
||||
false,
|
||||
'You are trying to dispose $Container that has been already disposed or never existed',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.initializing) {
|
||||
// if no initialized dependencies has been passed
|
||||
// then this is a normal drop
|
||||
if (initializedDeps == null && initializedScope == null) {
|
||||
Logger.warning(
|
||||
'$Container calls dispose method while initializing. '
|
||||
'This is a weird situation and can lead to unexpected behaviour.',
|
||||
);
|
||||
|
||||
final currentCompleter = _waitLifecycleCompleter;
|
||||
if (currentCompleter != null) {
|
||||
throw ScopeException(
|
||||
'Scope is already waiting for initialization in order to be disposed again. '
|
||||
'Probably you have called `drop` method without await a few times in a row.',
|
||||
);
|
||||
}
|
||||
final completer = Completer.sync();
|
||||
_waitLifecycleCompleter = completer;
|
||||
|
||||
final removeListener = listenState((state) {
|
||||
if (state.available) {
|
||||
completer.complete();
|
||||
} else {
|
||||
completer.completeError(
|
||||
ScopeError(
|
||||
'Unexpected state ($state) after ${ScopeState.initializing},'
|
||||
' must be ${ScopeState.available}',
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
try {
|
||||
await completer.future;
|
||||
} on Object catch (e, s) {
|
||||
Error.throwWithStackTrace(
|
||||
ScopeError(
|
||||
'$e\n'
|
||||
'Unexpected exception when were waiting for initialization during dispose. '
|
||||
'This is definitely an error in the library,'
|
||||
' please contact an owner, if you see this message.',
|
||||
),
|
||||
s,
|
||||
);
|
||||
} finally {
|
||||
_waitLifecycleCompleter = null;
|
||||
removeListener();
|
||||
Logger.debug(
|
||||
'Wait for scope initialization has completed, state=$state',
|
||||
);
|
||||
if (!state.available) {
|
||||
throw ScopeError(
|
||||
'Scope dispose waited for initialization of the previous scope state, '
|
||||
'it\'s expected to be ${ScopeState.available}, '
|
||||
'but it appears to be $state. '
|
||||
'This is definitely an error in the library,'
|
||||
' please contact an owner, if you see this message.',
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Logger.warning(
|
||||
'$Container calls dispose method, because of some exception. '
|
||||
'See stacktrace below for more details.',
|
||||
);
|
||||
}
|
||||
// If no initialized dependencies has been passed it means this is an internal drop.
|
||||
// It happens only when some Exception appeared during init.
|
||||
// In this case we do not wait for availability and do the drop.
|
||||
}
|
||||
|
||||
final scope = this.scope as Container? ?? initializedScope;
|
||||
if (scope == null) {
|
||||
throw ScopeError(
|
||||
'$Container must not be null if scope state is $state',
|
||||
);
|
||||
}
|
||||
|
||||
_scopeObserverInternal.onScopeStartDispose(scope);
|
||||
|
||||
_disposing();
|
||||
|
||||
Logger.debug('Dispose children');
|
||||
final listeners = [...scope._disposeListeners];
|
||||
await Future.wait<void>(listeners.map((e) => e.call()));
|
||||
|
||||
// If there were already initialized dependencies
|
||||
// then we dispose only them and do not bother others
|
||||
final queue = (initializedDeps ?? scope.initializeQueue).reversed;
|
||||
for (final depSet in queue) {
|
||||
await Future.wait(
|
||||
depSet.map(
|
||||
(dep) {
|
||||
final scopeType = Container;
|
||||
final depType = dep.runtimeType;
|
||||
Logger.debug('($scopeType) Disposing: $depType');
|
||||
return dep
|
||||
._dispose()
|
||||
.then((_) => Logger.debug('($scopeType) Disposed: $depType'))
|
||||
// ignore: avoid_types_on_closure_parameters
|
||||
.catchError((Object e, StackTrace s) {
|
||||
Logger.error(
|
||||
'Exception happened during $depType dispose ($scopeType). '
|
||||
'This dependency is skipped and dispose continued.',
|
||||
e,
|
||||
s,
|
||||
);
|
||||
_scopeObserverInternal.onScopeDisposeDepFailed(
|
||||
scope,
|
||||
dep,
|
||||
e,
|
||||
s,
|
||||
);
|
||||
// We continue disposing even in case of an exception
|
||||
// because we have to complete scope dispose at least for
|
||||
// dependencies without exceptions and for the scope itself.
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
scope._unregister();
|
||||
_clearObservers(scope);
|
||||
await _disposed();
|
||||
_scopeObserverInternal.onScopeDisposed(scope);
|
||||
}
|
||||
|
||||
// ignore: use_setters_to_change_properties
|
||||
void _updateScope(ScopeState<Scope> state) {
|
||||
Logger.debug('$Container state: $state');
|
||||
_updateState(state);
|
||||
}
|
||||
|
||||
void _initializing() => _updateScope(ScopeState.initializing());
|
||||
|
||||
Future<void> _disposed() async {
|
||||
try {
|
||||
_updateScope(ScopeState.none()); // must be the first call in this method
|
||||
} on NotifyListenerError catch (e, s) {
|
||||
Logger.error('Some listeners thrown an exception during dispose', e, s);
|
||||
}
|
||||
}
|
||||
|
||||
void _available(Scope scope) {
|
||||
try {
|
||||
_updateScope(ScopeState.available(
|
||||
scope: scope)); // must be the first call in this method
|
||||
} on NotifyListenerError catch (e, s) {
|
||||
Logger.error('Some listeners thrown an exception during init', e, s);
|
||||
}
|
||||
}
|
||||
|
||||
void _disposing() => _updateScope(ScopeState.disposing());
|
||||
|
||||
void _prepareObservers(Container scope) {
|
||||
scope._depObserver._observers = _depObservers;
|
||||
scope._asyncDepObserver._observers = _asyncDepObservers;
|
||||
scope._asyncDepObserver._asyncDepObservers = _asyncDepObservers;
|
||||
}
|
||||
|
||||
void _clearObservers(Container scope) {
|
||||
scope._depObserver._observers = null;
|
||||
scope._asyncDepObserver._observers = null;
|
||||
scope._asyncDepObserver._asyncDepObservers = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
part of 'base_scope_container.dart';
|
||||
|
||||
/// Abstract base class for creating custom dependency types.
|
||||
///
|
||||
/// Use this class as a base when you need to create a specialized dependency
|
||||
/// with custom behavior, validation, or additional functionality beyond what
|
||||
/// the standard [Dep] class provides.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// class DatabaseDep extends CustomDep<Database> {
|
||||
/// DatabaseDep(BaseScopeContainer scope, String connectionString)
|
||||
/// : super(scope, () => Database.connect(connectionString));
|
||||
///
|
||||
/// @override
|
||||
/// Database get get {
|
||||
/// final db = super.get;
|
||||
/// if (!db.isConnected) {
|
||||
/// throw StateError('Database connection is not available');
|
||||
/// }
|
||||
/// return db;
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
abstract class CustomDep<Value> extends Dep<Value> {
|
||||
/// Creates a custom dependency.
|
||||
///
|
||||
/// [scope] - The scope container that owns this dependency
|
||||
/// [builder] - Factory function that creates the dependency value
|
||||
/// [name] - Optional name for debugging purposes
|
||||
/// [observer] - Optional observer for monitoring dependency lifecycle
|
||||
CustomDep(
|
||||
BaseScopeContainer scope,
|
||||
DepBuilder<Value> builder, {
|
||||
String? name,
|
||||
DepObserverInternal? observer,
|
||||
}) : super._(scope, builder, name: name, observer: observer);
|
||||
}
|
||||
|
||||
/// Abstract base class for creating custom async dependency types.
|
||||
///
|
||||
/// Use this class as a base when you need to create a specialized async dependency
|
||||
/// that requires initialization and disposal with custom behavior or validation.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// class HttpClientDep extends CustomAsyncDep<HttpClient> {
|
||||
/// HttpClientDep(BaseScopeContainer scope, {required String baseUrl})
|
||||
/// : super(
|
||||
/// scope,
|
||||
/// () => HttpClient()..baseUrl = baseUrl,
|
||||
/// init: (client) async {
|
||||
/// await client.authenticate();
|
||||
/// print('HTTP client authenticated');
|
||||
/// },
|
||||
/// dispose: (client) async {
|
||||
/// await client.logout();
|
||||
/// client.close();
|
||||
/// print('HTTP client disposed');
|
||||
/// },
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
abstract class CustomAsyncDep<Value> extends AsyncDep<Value> {
|
||||
/// Creates a custom async dependency.
|
||||
///
|
||||
/// [scope] - The scope container that owns this dependency
|
||||
/// [builder] - Factory function that creates the dependency value
|
||||
/// [init] - Async callback called during dependency initialization
|
||||
/// [dispose] - Async callback called during dependency disposal
|
||||
/// [name] - Optional name for debugging purposes
|
||||
/// [observer] - Optional observer for monitoring dependency lifecycle
|
||||
CustomAsyncDep(
|
||||
BaseScopeContainer scope,
|
||||
DepBuilder<Value> builder, {
|
||||
required AsyncDepCallback<Value> init,
|
||||
required AsyncDepCallback<Value> dispose,
|
||||
String? name,
|
||||
AsyncDepObserverInternal? observer,
|
||||
}) : super._(scope, builder,
|
||||
name: name, observer: observer, init: init, dispose: dispose);
|
||||
}
|
||||
|
|
@ -0,0 +1,168 @@
|
|||
part of 'base_scope_container.dart';
|
||||
|
||||
/// A factory method that creates an entity in [Dep].
|
||||
typedef DepBuilder<Value> = Value Function();
|
||||
|
||||
/// Callback creates an entity based on current [BaseScopeContainer]
|
||||
/// and will be used as a primary factory for creating an entity.
|
||||
typedef OverrideDepBuilder<Container extends BaseScopeContainer, Value> = Value
|
||||
Function(Container container);
|
||||
|
||||
/// Extract meta information about [Dep],
|
||||
/// e.x. DepId.
|
||||
/// This extra class helps to stay the interface of
|
||||
/// the main [Dep] clean an contain only dep.get method.
|
||||
///
|
||||
/// This class must be used only locally and must not be
|
||||
/// assigned to any field or global final or variable.
|
||||
class DepMeta<T> {
|
||||
final Dep<T> _dep;
|
||||
|
||||
DepMeta(Dep<T> dep) : _dep = dep;
|
||||
|
||||
DepId get id => _dep._id;
|
||||
}
|
||||
|
||||
/// A description for a dependency.
|
||||
/// Basically this is a factory class for any custom entity.
|
||||
class Dep<Value> {
|
||||
final String? _name;
|
||||
|
||||
final BaseScopeContainer _scope;
|
||||
final DepBuilder<Value> _builder;
|
||||
final DepObserverInternal? _observer;
|
||||
|
||||
late final DepId _id;
|
||||
|
||||
_DepValue<Value>? _value;
|
||||
|
||||
var _registered = false;
|
||||
|
||||
Dep._(
|
||||
this._scope,
|
||||
this._builder, {
|
||||
String? name,
|
||||
DepObserverInternal? observer,
|
||||
}) : _name = name,
|
||||
_observer = observer {
|
||||
_id = DepId(Value, hashCode, _name);
|
||||
_scope._registerDep(this);
|
||||
_registered = true;
|
||||
}
|
||||
|
||||
/// Returns an entity by request
|
||||
Value get get {
|
||||
if (!_registered) {
|
||||
throw ScopeException(
|
||||
'You are trying to get an instance of $Value '
|
||||
'from the Dep ${_name ?? hashCode.toString()}, '
|
||||
'but the Scope ${_scope._name ?? _scope.hashCode.toString()} has been disposed. '
|
||||
'Probably you stored an instance of the Dep '
|
||||
'somewhere away from the Scope. '
|
||||
'Do not keep a Dep instance separately from it\'s Scope, '
|
||||
'and access Dep instance only directly from the Scope.',
|
||||
);
|
||||
}
|
||||
|
||||
final crtValue = _value;
|
||||
if (crtValue != null) {
|
||||
return crtValue.value;
|
||||
} else {
|
||||
try {
|
||||
_observer?.onValueStartCreate(this);
|
||||
final newValue = _builder();
|
||||
|
||||
_value = _DepValue(newValue);
|
||||
_observer?.onValueCreated(this, newValue);
|
||||
return newValue;
|
||||
} on Object catch (e, s) {
|
||||
_observer?.onValueCreateFailed(this, e, s);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _unregister() {
|
||||
if (!_registered) {
|
||||
throw ScopeError(
|
||||
'Dep._unregister() is called when it\'s not really registered yet — '
|
||||
'this is definitely an error in the library, '
|
||||
'please contact an owner, if you see this message.',
|
||||
);
|
||||
}
|
||||
final value = _value?.value;
|
||||
_value = null;
|
||||
_observer?.onValueCleared(this, value);
|
||||
_registered = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// A description for a dependency that implements [AsyncLifecycle].
|
||||
/// This dependency will be initialized and disposed along with [BaseScopeContainer].
|
||||
class AsyncDep<Value> extends Dep<Value> {
|
||||
final AsyncDepCallback<Value> _initCallback;
|
||||
final AsyncDepCallback<Value> _disposeCallback;
|
||||
|
||||
final AsyncDepObserverInternal? _asyncDepObserver;
|
||||
|
||||
var _initialized = false;
|
||||
|
||||
AsyncDep._(
|
||||
BaseScopeContainer scope,
|
||||
DepBuilder<Value> builder, {
|
||||
required AsyncDepCallback<Value> init,
|
||||
required AsyncDepCallback<Value> dispose,
|
||||
String? name,
|
||||
AsyncDepObserverInternal? observer,
|
||||
}) : _initCallback = init,
|
||||
_disposeCallback = dispose,
|
||||
_asyncDepObserver = observer,
|
||||
super._(scope, builder, name: name, observer: observer);
|
||||
|
||||
Future<void> _init() async {
|
||||
final value = super.get;
|
||||
try {
|
||||
_asyncDepObserver?.onDepStartInitialize(this);
|
||||
await _initCallback(value);
|
||||
_initialized = true;
|
||||
_asyncDepObserver?.onDepInitialized(this);
|
||||
} on Object catch (e, s) {
|
||||
_asyncDepObserver?.onDepInitializeFailed(this, e, s);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _dispose() async {
|
||||
assert(
|
||||
_initialized,
|
||||
'Dispose of $runtimeType has been called without initialization',
|
||||
);
|
||||
final value = get;
|
||||
try {
|
||||
_initialized = false;
|
||||
_asyncDepObserver?.onDepStartDispose(this);
|
||||
await _disposeCallback(value);
|
||||
_asyncDepObserver?.onDepDisposed(this);
|
||||
} on Object catch (e, s) {
|
||||
_asyncDepObserver?.onDepDisposeFailed(this, e, s);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Value get get {
|
||||
assert(
|
||||
_initialized,
|
||||
'You have forgotten to add $runtimeType to initializeQueue or it '
|
||||
'has been used before initialization by another dep. '
|
||||
'Try to reorder deps in initializeQueue.',
|
||||
);
|
||||
return super.get;
|
||||
}
|
||||
}
|
||||
|
||||
class _DepValue<T> {
|
||||
final T value;
|
||||
|
||||
const _DepValue(this.value);
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
part of 'observers.dart';
|
||||
|
||||
@Deprecated('Use ScopeObserver instead')
|
||||
abstract class ScopeListener implements ScopeObserver {}
|
||||
|
||||
@Deprecated('Use DepObserver instead')
|
||||
abstract class DepListener implements DepObserver {}
|
||||
|
||||
@Deprecated('Use AsyncDepObserver instead')
|
||||
abstract class AsyncDepListener extends DepListener
|
||||
implements AsyncDepObserver {}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
/// A ready-only unique identifier of a [Dep].
|
||||
/// An instance of DepId is always the same for
|
||||
/// the same Dep instance.
|
||||
class DepId {
|
||||
final Type valueType;
|
||||
final int depHashCode;
|
||||
final String? name;
|
||||
|
||||
const DepId(this.valueType, this.depHashCode, this.name);
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
'${name ?? valueType}${name != null ? '[$valueType]' : ''}[$depHashCode]';
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
/// A ready-only unique identifier of a [ScopeContainer].
|
||||
/// An instance of ScopeId is always the same for
|
||||
/// the same [ScopeContainer] instance.
|
||||
class ScopeId {
|
||||
final Type type;
|
||||
final int scopeHashCode;
|
||||
final String? name;
|
||||
|
||||
const ScopeId(this.type, this.scopeHashCode, this.name);
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
'${name ?? type}${name != null ? '[$type]' : ''}[$scopeHashCode]';
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
part of '../../base_scope_container.dart';
|
||||
|
||||
/// Extract meta information about [BaseScopeContainer],
|
||||
/// e.x. ScopeId.
|
||||
/// This extra class helps to stay the interface of
|
||||
/// the main [BaseScopeContainer] clean and contain only dep-related method.
|
||||
///
|
||||
/// This class must be used only locally and must not be
|
||||
/// assigned to any field or global final or variable.
|
||||
class ScopeMeta {
|
||||
final BaseScopeContainer _scope;
|
||||
|
||||
const ScopeMeta(BaseScopeContainer scope) : _scope = scope;
|
||||
|
||||
ScopeId get id => _scope._id;
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
/// A ready-only meta info of created value in a [Dep].
|
||||
class ValueMeta {
|
||||
final Type valueType;
|
||||
final int valueHashCode;
|
||||
|
||||
const ValueMeta(this.valueType, this.valueHashCode);
|
||||
|
||||
static ValueMeta? build(Object? object) =>
|
||||
object == null ? null : ValueMeta(object.runtimeType, object.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => '$valueType[$valueHashCode]';
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
import 'models/dep_id.dart';
|
||||
import 'models/scope_id.dart';
|
||||
import 'models/value_meta.dart';
|
||||
|
||||
part 'listeners.dart';
|
||||
|
||||
/// Interface for observing [Scope] state.
|
||||
///
|
||||
/// To observe [Scope] state create your implementation of this interface
|
||||
/// and pass it to your [ScopeHolder].
|
||||
abstract class ScopeObserver {
|
||||
void onScopeStartInitialize(ScopeId scope);
|
||||
|
||||
void onScopeInitialized(ScopeId scope);
|
||||
|
||||
void onScopeInitializeFailed(
|
||||
ScopeId scope,
|
||||
Object exception,
|
||||
StackTrace stackTrace,
|
||||
);
|
||||
|
||||
void onScopeStartDispose(ScopeId scope);
|
||||
|
||||
void onScopeDisposed(ScopeId scope);
|
||||
|
||||
/// The method is called when dispose of the [dep] has failed
|
||||
/// during the dispose phase of the [scope].
|
||||
///
|
||||
/// This method can be called many times during dispose phase.
|
||||
void onScopeDisposeDepFailed(
|
||||
ScopeId scope,
|
||||
DepId dep,
|
||||
Object exception,
|
||||
StackTrace stackTrace,
|
||||
);
|
||||
|
||||
const ScopeObserver._();
|
||||
}
|
||||
|
||||
/// Interface for observing [Dep] state.
|
||||
///
|
||||
/// To observe [Dep] state create your implementation of this interface
|
||||
/// and pass it to your [ScopeHolder].
|
||||
abstract class DepObserver {
|
||||
void onValueStartCreate(ScopeId scope, DepId dep);
|
||||
|
||||
void onValueCreated(ScopeId scope, DepId dep, ValueMeta? valueMeta);
|
||||
|
||||
void onValueCreateFailed(
|
||||
ScopeId scope,
|
||||
DepId dep,
|
||||
Object exception,
|
||||
StackTrace stackTrace,
|
||||
);
|
||||
|
||||
void onValueCleared(ScopeId scope, DepId dep, ValueMeta? valueMeta);
|
||||
|
||||
const DepObserver._();
|
||||
}
|
||||
|
||||
/// Interface for observing [AsyncDep] state.
|
||||
///
|
||||
/// To observe [AsyncDep] state create your implementation of this interface
|
||||
/// and pass it to your [ScopeHolder].
|
||||
abstract class AsyncDepObserver implements DepObserver {
|
||||
void onDepStartInitialize(ScopeId scope, DepId dep);
|
||||
|
||||
void onDepInitialized(ScopeId scope, DepId dep);
|
||||
|
||||
void onDepStartDispose(ScopeId scope, DepId dep);
|
||||
|
||||
void onDepDisposed(ScopeId scope, DepId dep);
|
||||
|
||||
void onDepInitializeFailed(
|
||||
ScopeId scope,
|
||||
DepId dep,
|
||||
Object exception,
|
||||
StackTrace stackTrace,
|
||||
);
|
||||
|
||||
void onDepDisposeFailed(
|
||||
ScopeId scope,
|
||||
DepId dep,
|
||||
Object exception,
|
||||
StackTrace stackTrace,
|
||||
);
|
||||
|
||||
const AsyncDepObserver._();
|
||||
}
|
||||
|
|
@ -0,0 +1,299 @@
|
|||
part of '../base_scope_container.dart';
|
||||
|
||||
/// DO NOT USE THIS CLASS MANUALLY
|
||||
///
|
||||
/// [RawScopeObserver] implementation for [CoreScopeHolder] that notifies
|
||||
/// [ScopeObserver]s
|
||||
class ScopeObserverInternal implements RawScopeObserver {
|
||||
final List<ScopeObserver>? _observers;
|
||||
|
||||
const ScopeObserverInternal(this._observers);
|
||||
|
||||
@override
|
||||
void onScopeStartInitialize(BaseScopeContainer scope) {
|
||||
RawScopeObserver.override?.safeNotify(
|
||||
(listener) => listener.onScopeStartInitialize(scope),
|
||||
);
|
||||
_safeNotifyAll<ScopeObserver>(
|
||||
_observers,
|
||||
(listener) => listener.onScopeStartInitialize(scope._id),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void onScopeInitialized(BaseScopeContainer scope) {
|
||||
RawScopeObserver.override?.safeNotify(
|
||||
(listener) => listener.onScopeInitialized(scope),
|
||||
);
|
||||
_safeNotifyAll<ScopeObserver>(
|
||||
_observers,
|
||||
(listener) => listener.onScopeInitialized(scope._id),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void onScopeInitializeFailed(
|
||||
BaseScopeContainer scope,
|
||||
Object exception,
|
||||
StackTrace stackTrace,
|
||||
) {
|
||||
RawScopeObserver.override?.safeNotify(
|
||||
(listener) => listener.onScopeInitializeFailed(
|
||||
scope,
|
||||
exception,
|
||||
stackTrace,
|
||||
),
|
||||
);
|
||||
_safeNotifyAll<ScopeObserver>(
|
||||
_observers,
|
||||
(listener) => listener.onScopeInitializeFailed(
|
||||
scope._id,
|
||||
exception,
|
||||
stackTrace,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void onScopeStartDispose(BaseScopeContainer scope) {
|
||||
RawScopeObserver.override?.safeNotify(
|
||||
(listener) => listener.onScopeStartDispose(scope),
|
||||
);
|
||||
_safeNotifyAll<ScopeObserver>(
|
||||
_observers,
|
||||
(listener) => listener.onScopeStartDispose(scope._id),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void onScopeDisposed(BaseScopeContainer scope) {
|
||||
RawScopeObserver.override?.safeNotify(
|
||||
(listener) => listener.onScopeDisposed(scope),
|
||||
);
|
||||
_safeNotifyAll<ScopeObserver>(
|
||||
_observers,
|
||||
(listener) => listener.onScopeDisposed(scope._id),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void onScopeDisposeDepFailed(
|
||||
BaseScopeContainer scope,
|
||||
Dep dep,
|
||||
Object exception,
|
||||
StackTrace stackTrace,
|
||||
) {
|
||||
RawScopeObserver.override?.safeNotify(
|
||||
(listener) => listener.onScopeDisposeDepFailed(
|
||||
scope,
|
||||
dep,
|
||||
exception,
|
||||
stackTrace,
|
||||
),
|
||||
);
|
||||
_safeNotifyAll<ScopeObserver>(
|
||||
_observers,
|
||||
(listener) => listener.onScopeDisposeDepFailed(
|
||||
scope._id,
|
||||
dep._id,
|
||||
exception,
|
||||
stackTrace,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// DO NOT USE THIS CLASS MANUALLY
|
||||
///
|
||||
/// [RawDepObserver] implementation for [Dep] that notifies
|
||||
/// [DepObserver]s
|
||||
class DepObserverInternal {
|
||||
final BaseScopeContainer _scope;
|
||||
List<DepObserver>? _observers;
|
||||
|
||||
DepObserverInternal(this._scope);
|
||||
|
||||
void onValueStartCreate(Dep dep) {
|
||||
RawDepObserver.override?.safeNotify(
|
||||
(listener) => listener.onValueStartCreate(_scope, dep),
|
||||
);
|
||||
_safeNotifyAll<DepObserver>(
|
||||
_observers,
|
||||
(listener) => listener.onValueStartCreate(_scope._id, dep._id),
|
||||
);
|
||||
}
|
||||
|
||||
void onValueCreated(Dep dep, Object? value) {
|
||||
RawDepObserver.override?.safeNotify(
|
||||
(listener) => listener.onValueCreated(_scope, dep, value),
|
||||
);
|
||||
_safeNotifyAll<DepObserver>(
|
||||
_observers,
|
||||
(listener) => listener.onValueCreated(
|
||||
_scope._id,
|
||||
dep._id,
|
||||
ValueMeta.build(value),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void onValueCreateFailed(
|
||||
Dep dep,
|
||||
Object exception,
|
||||
StackTrace stackTrace,
|
||||
) {
|
||||
RawDepObserver.override?.safeNotify(
|
||||
(listener) => listener.onValueCreateFailed(
|
||||
_scope,
|
||||
dep,
|
||||
exception,
|
||||
stackTrace,
|
||||
),
|
||||
);
|
||||
_safeNotifyAll<DepObserver>(
|
||||
_observers,
|
||||
(listener) => listener.onValueCreateFailed(
|
||||
_scope._id,
|
||||
dep._id,
|
||||
exception,
|
||||
stackTrace,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void onValueCleared(Dep dep, Object? value) {
|
||||
RawDepObserver.override?.safeNotify(
|
||||
(listener) => listener.onValueCleared(_scope, dep, value),
|
||||
);
|
||||
_safeNotifyAll<DepObserver>(
|
||||
_observers,
|
||||
(listener) => listener.onValueCleared(
|
||||
_scope._id,
|
||||
dep._id,
|
||||
ValueMeta.build(value),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// DO NOT USE THIS CLASS MANUALLY
|
||||
///
|
||||
/// [RawDepObserver] implementation for [AsyncDep] that notifies
|
||||
/// [AsyncDepObserver]s
|
||||
class AsyncDepObserverInternal extends DepObserverInternal {
|
||||
List<AsyncDepObserver>? _asyncDepObservers;
|
||||
|
||||
AsyncDepObserverInternal(BaseScopeContainer scope) : super(scope);
|
||||
|
||||
void onDepStartInitialize(Dep dep) {
|
||||
RawAsyncDepObserver.override?.safeNotify(
|
||||
(listener) => listener.onDepStartInitialize(_scope, dep),
|
||||
);
|
||||
_safeNotifyAll<AsyncDepObserver>(
|
||||
_asyncDepObservers,
|
||||
(listener) => listener.onDepStartInitialize(_scope._id, dep._id),
|
||||
);
|
||||
}
|
||||
|
||||
void onDepInitialized(Dep dep) {
|
||||
RawAsyncDepObserver.override?.safeNotify(
|
||||
(listener) => listener.onDepInitialized(_scope, dep),
|
||||
);
|
||||
_safeNotifyAll<AsyncDepObserver>(
|
||||
_asyncDepObservers,
|
||||
(listener) => listener.onDepInitialized(_scope._id, dep._id),
|
||||
);
|
||||
}
|
||||
|
||||
void onDepStartDispose(Dep dep) {
|
||||
RawAsyncDepObserver.override?.safeNotify(
|
||||
(listener) => listener.onDepStartDispose(_scope, dep),
|
||||
);
|
||||
_safeNotifyAll<AsyncDepObserver>(
|
||||
_asyncDepObservers,
|
||||
(listener) => listener.onDepStartDispose(_scope._id, dep._id),
|
||||
);
|
||||
}
|
||||
|
||||
void onDepDisposed(Dep dep) {
|
||||
RawAsyncDepObserver.override?.safeNotify(
|
||||
(listener) => listener.onDepDisposed(_scope, dep),
|
||||
);
|
||||
_safeNotifyAll<AsyncDepObserver>(
|
||||
_asyncDepObservers,
|
||||
(listener) => listener.onDepDisposed(_scope._id, dep._id),
|
||||
);
|
||||
}
|
||||
|
||||
void onDepInitializeFailed(
|
||||
Dep dep,
|
||||
Object exception,
|
||||
StackTrace stackTrace,
|
||||
) {
|
||||
RawAsyncDepObserver.override?.safeNotify(
|
||||
(listener) => listener.onDepInitializeFailed(
|
||||
_scope,
|
||||
dep,
|
||||
exception,
|
||||
stackTrace,
|
||||
),
|
||||
);
|
||||
_safeNotifyAll<AsyncDepObserver>(
|
||||
_asyncDepObservers,
|
||||
(listener) => listener.onDepInitializeFailed(
|
||||
_scope._id,
|
||||
dep._id,
|
||||
exception,
|
||||
stackTrace,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void onDepDisposeFailed(
|
||||
Dep dep,
|
||||
Object exception,
|
||||
StackTrace stackTrace,
|
||||
) {
|
||||
RawAsyncDepObserver.override?.safeNotify(
|
||||
(listener) => listener.onDepDisposeFailed(
|
||||
_scope,
|
||||
dep,
|
||||
exception,
|
||||
stackTrace,
|
||||
),
|
||||
);
|
||||
_safeNotifyAll<AsyncDepObserver>(
|
||||
_asyncDepObservers,
|
||||
(listener) => listener.onDepDisposeFailed(
|
||||
_scope._id,
|
||||
dep._id,
|
||||
exception,
|
||||
stackTrace,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension _SafeNotification<T> on T {
|
||||
void safeNotify(void Function(T it) callback) {
|
||||
try {
|
||||
callback(this);
|
||||
} on Object catch (e, s) {
|
||||
Logger.warning(
|
||||
'An error occurred in a listener $T#$hashCode: $e\n$s',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _safeNotifyAll<T>(
|
||||
List<T>? listeners,
|
||||
void Function(T listener) callback,
|
||||
) {
|
||||
if (listeners == null) {
|
||||
return;
|
||||
}
|
||||
for (final listener in listeners) {
|
||||
listener.safeNotify(callback);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
part of 'raw_observers.dart';
|
||||
|
||||
@Deprecated('Use RawScopeObserver instead')
|
||||
abstract class RawScopeListener extends RawScopeObserver {
|
||||
static set override(RawScopeObserver? override) =>
|
||||
RawScopeObserver.override = override;
|
||||
|
||||
static RawScopeObserver? get override => RawScopeObserver.override;
|
||||
|
||||
RawScopeListener._() : super._();
|
||||
}
|
||||
|
||||
@Deprecated('Use RawDepObserver instead')
|
||||
abstract class RawDepListener extends RawDepObserver {
|
||||
static set override(RawDepObserver? override) =>
|
||||
RawDepObserver.override = override;
|
||||
|
||||
static RawDepObserver? get override => RawDepObserver.override;
|
||||
}
|
||||
|
||||
@Deprecated('Use RawAsyncDepObserver instead')
|
||||
abstract class RawAsyncDepListener extends RawAsyncDepObserver
|
||||
implements RawDepListener {
|
||||
static set override(RawAsyncDepObserver? override) =>
|
||||
RawAsyncDepObserver.override = override;
|
||||
|
||||
static RawAsyncDepObserver? get override => RawAsyncDepObserver.override;
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
import '../base_scope_container.dart';
|
||||
|
||||
part 'raw_listeners.dart';
|
||||
|
||||
/// Consider using [ScopeObserver], [DepObserver] and [AsyncDepObserver] instead — these are Observers with a safe read-only access.
|
||||
/// [RawScopeObserver], [RawDepObserver] and [RawAsyncDepObserver] are an advanced direct access for rare cases.
|
||||
/// So if you are not sure if this is the right choice for you then it's probably not.
|
||||
///
|
||||
/// [RawScopeObserver] is the Observer with a direct access to [BaseScopeContainer] and [Dep].
|
||||
abstract class RawScopeObserver {
|
||||
static RawScopeObserver? override;
|
||||
|
||||
RawScopeObserver._();
|
||||
|
||||
void onScopeStartInitialize(BaseScopeContainer scope);
|
||||
|
||||
void onScopeInitialized(BaseScopeContainer scope);
|
||||
|
||||
void onScopeInitializeFailed(
|
||||
BaseScopeContainer scope,
|
||||
Object exception,
|
||||
StackTrace stackTrace,
|
||||
);
|
||||
|
||||
void onScopeStartDispose(BaseScopeContainer scope);
|
||||
|
||||
void onScopeDisposed(BaseScopeContainer scope);
|
||||
|
||||
void onScopeDisposeDepFailed(
|
||||
BaseScopeContainer scope,
|
||||
Dep dep,
|
||||
Object exception,
|
||||
StackTrace stackTrace,
|
||||
);
|
||||
}
|
||||
|
||||
/// [RawDepObserver] is the Observer with a direct access to [BaseScopeContainer], [Dep] and created instance.
|
||||
/// More details in [RawScopeObserver]
|
||||
abstract class RawDepObserver {
|
||||
static RawDepObserver? override;
|
||||
|
||||
void onValueStartCreate(BaseScopeContainer scope, Dep dep);
|
||||
|
||||
void onValueCreated(BaseScopeContainer scope, Dep dep, Object? value);
|
||||
|
||||
void onValueCreateFailed(
|
||||
BaseScopeContainer scope,
|
||||
Dep dep,
|
||||
Object exception,
|
||||
StackTrace stackTrace,
|
||||
);
|
||||
|
||||
void onValueCleared(BaseScopeContainer scope, Dep dep, Object? value);
|
||||
}
|
||||
|
||||
/// [RawAsyncDepObserver] is the Observer with a direct access to [BaseScopeContainer] and [Dep].
|
||||
/// More details in [RawScopeObserver]
|
||||
abstract class RawAsyncDepObserver implements RawDepObserver {
|
||||
static RawAsyncDepObserver? override;
|
||||
|
||||
void onDepStartInitialize(BaseScopeContainer scope, Dep dep);
|
||||
|
||||
void onDepInitialized(BaseScopeContainer scope, Dep dep);
|
||||
|
||||
void onDepStartDispose(BaseScopeContainer scope, Dep dep);
|
||||
|
||||
void onDepDisposed(BaseScopeContainer scope, Dep dep);
|
||||
|
||||
void onDepInitializeFailed(
|
||||
BaseScopeContainer scope,
|
||||
Dep dep,
|
||||
Object exception,
|
||||
StackTrace stackTrace,
|
||||
);
|
||||
|
||||
void onDepDisposeFailed(
|
||||
BaseScopeContainer scope,
|
||||
Dep dep,
|
||||
Object exception,
|
||||
StackTrace stackTrace,
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
/// Class for diagnostic purposes.
|
||||
///
|
||||
/// Logging, state of scopes, info about dependencies.
|
||||
class ScopeObservatory {
|
||||
// Set this logger in order to log event in scopes.
|
||||
// By default it logs nothing.
|
||||
static ScopeLogger logger = const ScopeLogger();
|
||||
|
||||
const ScopeObservatory._();
|
||||
}
|
||||
|
||||
class ScopeLogger {
|
||||
const ScopeLogger();
|
||||
|
||||
void log(
|
||||
LogType type,
|
||||
Object message, {
|
||||
Object? exception,
|
||||
StackTrace? stackTrace,
|
||||
}) {
|
||||
// Override in order to log event in scopes
|
||||
}
|
||||
}
|
||||
|
||||
enum LogType {
|
||||
debug,
|
||||
info,
|
||||
warning,
|
||||
error,
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import 'scope_observatory.dart';
|
||||
|
||||
class Logger {
|
||||
static void debug(Object message) =>
|
||||
ScopeObservatory.logger.log(LogType.debug, message);
|
||||
|
||||
static void info(Object message) =>
|
||||
ScopeObservatory.logger.log(LogType.info, message);
|
||||
|
||||
static void warning(Object message) =>
|
||||
ScopeObservatory.logger.log(LogType.warning, message);
|
||||
|
||||
static void error(Object message, Object exception, StackTrace stackTrace) =>
|
||||
ScopeObservatory.logger.log(LogType.error, message,
|
||||
exception: exception, stackTrace: stackTrace);
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
import 'base_scope_container.dart';
|
||||
|
||||
/// Root [BaseScopeContainer] without parent scope.
|
||||
///
|
||||
/// {@macro base_scope_container}
|
||||
abstract class ScopeContainer extends BaseScopeContainer {
|
||||
ScopeContainer({String? name}) : super(name: name) {
|
||||
_initializeQueueNoDuplications(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// {@macro child_scope_container}
|
||||
abstract class ChildScopeContainer<Parent extends Scope>
|
||||
extends BaseScopeContainer with ChildScopeContainerMixin<Parent> {
|
||||
ChildScopeContainer({
|
||||
required Parent parent,
|
||||
String? name,
|
||||
}) : super(name: name) {
|
||||
this.parent = parent;
|
||||
_initializeQueueNoDuplications(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// {@macro data_scope_container}
|
||||
abstract class DataScopeContainer<Data extends Object>
|
||||
extends BaseScopeContainer with DataScopeContainerMixin<Data> {
|
||||
DataScopeContainer({
|
||||
required Data data,
|
||||
String? name,
|
||||
}) : super(name: name) {
|
||||
this.data = data;
|
||||
_initializeQueueNoDuplications(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// Combines [ChildScopeContainer] and [DataScopeContainer].
|
||||
///
|
||||
/// [ChildScopeContainer]:
|
||||
/// {@macro child_scope_container}
|
||||
///
|
||||
/// [DataScopeContainer]:
|
||||
/// {@macro data_scope_container}
|
||||
abstract class ChildDataScopeContainer<Parent extends Scope,
|
||||
Data extends Object> extends BaseScopeContainer
|
||||
with ChildScopeContainerMixin<Parent>, DataScopeContainerMixin<Data> {
|
||||
ChildDataScopeContainer({
|
||||
required Parent parent,
|
||||
required Data data,
|
||||
String? name,
|
||||
}) : super(name: name) {
|
||||
this.data = data;
|
||||
this.parent = parent;
|
||||
_initializeQueueNoDuplications(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if no duplicated async dependency instances
|
||||
/// added into initializeQueue.
|
||||
void _initializeQueueNoDuplications<Container extends BaseScopeContainer>(
|
||||
Container scope) {
|
||||
// ignore: invalid_use_of_protected_member
|
||||
final deps = scope.initializeQueue.expand((depSet) => depSet);
|
||||
final counter = <AsyncDep, int>{};
|
||||
for (final dep in deps) {
|
||||
counter[dep] = (counter[dep] ?? 0) + 1;
|
||||
}
|
||||
final duplications = counter.entries.where((entry) => entry.value > 1);
|
||||
assert(
|
||||
duplications.isEmpty,
|
||||
'(${scope.runtimeType}) Following async dependencies has been added '
|
||||
'to initializeQueue multiple times: '
|
||||
'[${duplications.map((e) => '${e.key.runtimeType}: ${e.value}').join(', ')}]',
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,456 @@
|
|||
part of 'base_scope_container.dart';
|
||||
|
||||
/// Simple holder for creating [BaseScopeHolder].
|
||||
///
|
||||
/// Here is an example:
|
||||
/// ```
|
||||
/// class SomeScopeHolder extends ScopeHolder<SomeScopeContainer> {}
|
||||
/// ```
|
||||
/// You also have to declare SomeScopeContainer, see [BaseScopeContainer].
|
||||
///
|
||||
/// Now you have to create an instance of this holder and use it
|
||||
/// to create/dispose scope and to communicate with it:
|
||||
/// ```
|
||||
/// final someScopeHolder = SomeScopeHolder();
|
||||
///
|
||||
/// await someScopeHolder.create();
|
||||
///
|
||||
/// final scope = someScopeHolder.scope;
|
||||
/// // Now an instance of a scope is available
|
||||
/// print(someScopeHolder.scope?.runtimeType); // SomeScopeContainer
|
||||
///
|
||||
/// // Access [ScopeContainer] only locally. Do not store it in fields.
|
||||
/// if(scope != null) {
|
||||
/// scope.appManager.someMethod();
|
||||
/// }
|
||||
///
|
||||
/// await someScopeHolder.drop();
|
||||
/// // Now scope is not available, so it's null
|
||||
/// print(someScopeHolder.scope?.runtimeType); // null
|
||||
///
|
||||
/// // You can also subscribe to changes of a scope
|
||||
/// someScopeHolder.listen((scope) {
|
||||
/// if(scope != null) {
|
||||
/// // scope exists and can be used
|
||||
/// } else {
|
||||
/// // scope is not available and disposed
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// If you need to differentiate [BaseScopeContainer] and it's abstract interface
|
||||
/// then you better use [BaseScopeHolder] directly.
|
||||
abstract class ScopeHolder<Container extends ScopeContainer>
|
||||
extends BaseScopeHolder<Container, Container> {
|
||||
ScopeHolder({
|
||||
@Deprecated('Use scopeObservers instead')
|
||||
List<ScopeListener>? scopeListeners,
|
||||
@Deprecated('Use depObservers instead') List<DepListener>? depListeners,
|
||||
@Deprecated('Use asyncDepObservers instead')
|
||||
List<AsyncDepListener>? asyncDepListeners,
|
||||
List<ScopeObserver>? scopeObservers,
|
||||
List<DepObserver>? depObservers,
|
||||
List<AsyncDepObserver>? asyncDepObservers,
|
||||
}) : super(
|
||||
scopeObservers: scopeObservers,
|
||||
depObservers: depObservers,
|
||||
asyncDepObservers: asyncDepObservers,
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
scopeListeners: scopeListeners,
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
depListeners: depListeners,
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
asyncDepListeners: asyncDepListeners,
|
||||
);
|
||||
}
|
||||
|
||||
/// Simple holder for creating [BaseChildScopeHolder].
|
||||
///
|
||||
/// Here is an example:
|
||||
/// ```
|
||||
/// class DependentScopeHolder
|
||||
/// extends ChildScopeHolder<DependentScopeContainer, SomeScopeContainer> {
|
||||
/// DependentScopeHolder(SomeScopeContainer parent) : super(parent);
|
||||
///
|
||||
/// @override
|
||||
/// DependentScopeContainer createContainer(SomeScopeContainer parent) =>
|
||||
/// DependentScopeContainer(
|
||||
/// parent: parent,
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
/// You also have to declare DependentScopeContainer, see [BaseScopeContainer].
|
||||
///
|
||||
/// As long as you [ScopeContainer] has parent, you must declare this holder
|
||||
/// inside parent [ScopeContainer]. Here is an example:
|
||||
///
|
||||
/// class SomeScopeContainer extends ScopeContainer {
|
||||
///
|
||||
/// // It must be late final as any [Dep] and you pass current [BaseScopeContainer]
|
||||
/// // as a parent inside your DependentScopeHolder.
|
||||
/// late final dependentScopeHolderDep = dep(() => DependentScopeHolder(this));
|
||||
///
|
||||
/// // ...
|
||||
/// }
|
||||
///
|
||||
/// If you need to differentiate [BaseScopeContainer] and it's abstract interface
|
||||
/// then you better use [BaseChildScopeHolder] directly.
|
||||
abstract class ChildScopeHolder<Container extends ChildScopeContainer<Parent>,
|
||||
Parent extends Scope>
|
||||
extends BaseChildScopeHolder<Container, Container, Parent> {
|
||||
ChildScopeHolder(
|
||||
Parent parent, {
|
||||
@Deprecated('Use scopeObservers instead')
|
||||
List<ScopeListener>? scopeListeners,
|
||||
@Deprecated('Use depObservers instead') List<DepListener>? depListeners,
|
||||
@Deprecated('Use asyncDepObservers instead')
|
||||
List<AsyncDepListener>? asyncDepListeners,
|
||||
List<ScopeObserver>? scopeObservers,
|
||||
List<DepObserver>? depObservers,
|
||||
List<AsyncDepObserver>? asyncDepObservers,
|
||||
}) : super(
|
||||
parent,
|
||||
scopeObservers: scopeObservers,
|
||||
depObservers: depObservers,
|
||||
asyncDepObservers: asyncDepObservers,
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
scopeListeners: scopeListeners,
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
depListeners: depListeners,
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
asyncDepListeners: asyncDepListeners,
|
||||
);
|
||||
}
|
||||
|
||||
/// Simple holder for creating [BaseDataScopeHolder].
|
||||
///
|
||||
/// Here is an example:
|
||||
/// ```
|
||||
/// class DataScopeHolder extends DataScopeHolder<DataScopeContainer, Data> {
|
||||
/// @override
|
||||
/// DataScopeContainer createContainer(Data data) => DataScopeContainer(data: data);
|
||||
/// }
|
||||
/// ```
|
||||
/// You also have to declare DataScopeContainer, see [BaseScopeContainer].
|
||||
///
|
||||
/// Working with scope:
|
||||
/// ```
|
||||
/// await dataScopeHolder.create(SomeData());
|
||||
/// // Scope exists here
|
||||
///
|
||||
/// await dataScopeHolder.drop();
|
||||
/// // Scope does not exist here
|
||||
/// ```
|
||||
///
|
||||
/// If you need to differentiate [BaseScopeContainer] and it's abstract interface
|
||||
/// then you better use [BaseDataScopeHolder] directly.
|
||||
abstract class DataScopeHolder<Container extends DataScopeContainer<Data>,
|
||||
Data extends Object>
|
||||
extends BaseDataScopeHolder<Container, Container, Data> {
|
||||
DataScopeHolder({
|
||||
@Deprecated('Use scopeObservers instead')
|
||||
List<ScopeListener>? scopeListeners,
|
||||
@Deprecated('Use depObservers instead') List<DepListener>? depListeners,
|
||||
@Deprecated('Use asyncDepObservers instead')
|
||||
List<AsyncDepListener>? asyncDepListeners,
|
||||
List<ScopeObserver>? scopeObservers,
|
||||
List<DepObserver>? depObservers,
|
||||
List<AsyncDepObserver>? asyncDepObservers,
|
||||
}) : super(
|
||||
scopeObservers: scopeObservers,
|
||||
depObservers: depObservers,
|
||||
asyncDepObservers: asyncDepObservers,
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
scopeListeners: scopeListeners,
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
depListeners: depListeners,
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
asyncDepListeners: asyncDepListeners,
|
||||
);
|
||||
}
|
||||
|
||||
/// Simple holder for creating [BaseChildDataScopeHolder].
|
||||
///
|
||||
/// Here is an example:
|
||||
/// ```
|
||||
/// class DataDependentScopeHolder
|
||||
/// extends ChildDataScopeHolder<DataDependentScopeContainer, SomeScopeContainer, SomeData> {
|
||||
/// DataDependentScopeHolder(SomeScopeContainer parent) : super(parent);
|
||||
///
|
||||
/// @override
|
||||
/// DataDependentScopeContainer createContainer(SomeScopeContainer parent, SomeData data) =>
|
||||
/// DataDependentScopeContainer(
|
||||
/// data: data,
|
||||
/// parent: parent,
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
/// You also have to declare DataDependentScopeContainer, see [BaseScopeContainer].
|
||||
///
|
||||
/// As long as you [ScopeContainer] has parent, you must declare this holder
|
||||
/// inside parent [ScopeContainer]. Here is an example:
|
||||
///
|
||||
/// class SomeScopeContainer extends ScopeContainer {
|
||||
///
|
||||
/// // It must be late final as any [Dep] and you pass current [BaseScopeContainer]
|
||||
/// // as a parent inside your DependentScopeHolder.
|
||||
/// late final dataDependentScopeHolderDep = dep(() => DataDependentScopeHolder(this));
|
||||
///
|
||||
/// // ...
|
||||
/// }
|
||||
///
|
||||
/// If you need to differentiate [BaseScopeContainer] and it's abstract interface
|
||||
/// then you better use [BaseChildDataScopeHolder] directly.
|
||||
abstract class ChildDataScopeHolder<
|
||||
Container extends ChildDataScopeContainer<Parent, Data>,
|
||||
Parent extends Scope,
|
||||
Data extends Object>
|
||||
extends BaseChildDataScopeHolder<Container, Container, Parent, Data> {
|
||||
ChildDataScopeHolder(
|
||||
Parent parent, {
|
||||
@Deprecated('Use scopeObservers instead')
|
||||
List<ScopeListener>? scopeListeners,
|
||||
@Deprecated('Use depObservers instead') List<DepListener>? depListeners,
|
||||
@Deprecated('Use asyncDepObservers instead')
|
||||
List<AsyncDepListener>? asyncDepListeners,
|
||||
List<ScopeObserver>? scopeObservers,
|
||||
List<DepObserver>? depObservers,
|
||||
List<AsyncDepObserver>? asyncDepObservers,
|
||||
}) : super(
|
||||
parent,
|
||||
scopeObservers: scopeObservers,
|
||||
depObservers: depObservers,
|
||||
asyncDepObservers: asyncDepObservers,
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
scopeListeners: scopeListeners,
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
depListeners: depListeners,
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
asyncDepListeners: asyncDepListeners,
|
||||
);
|
||||
}
|
||||
|
||||
/// Holder contains the state of a [BaseScopeContainer] — null or the scope itself.
|
||||
/// This is the core entity that provides access to the [BaseScopeContainer].
|
||||
///
|
||||
/// This holder allows to keep a [BaseScopeContainer] as an abstract interface [Scope].
|
||||
/// Your [BaseScopeContainer] have to implement this [Scope] interface.
|
||||
///
|
||||
/// You can use [ScopeHolder] if you don't need
|
||||
/// to differentiate [BaseScopeContainer] and it's interface.
|
||||
abstract class BaseScopeHolder<Scope, Container extends ScopeContainer>
|
||||
extends CoreScopeHolder<Scope, Container> {
|
||||
BaseScopeHolder({
|
||||
@Deprecated('Use scopeObservers instead')
|
||||
List<ScopeListener>? scopeListeners,
|
||||
@Deprecated('Use depObservers instead') List<DepListener>? depListeners,
|
||||
@Deprecated('Use asyncDepObservers instead')
|
||||
List<AsyncDepListener>? asyncDepListeners,
|
||||
List<ScopeObserver>? scopeObservers,
|
||||
List<DepObserver>? depObservers,
|
||||
List<AsyncDepObserver>? asyncDepObservers,
|
||||
}) : super(
|
||||
scopeObservers: scopeObservers,
|
||||
depObservers: depObservers,
|
||||
asyncDepObservers: asyncDepObservers,
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
scopeListeners: scopeListeners,
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
depListeners: depListeners,
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
asyncDepListeners: asyncDepListeners,
|
||||
);
|
||||
|
||||
@protected
|
||||
Container createContainer();
|
||||
|
||||
Future<void> create() => init(createContainer());
|
||||
}
|
||||
|
||||
/// Holder contains the state of a [ChildScopeContainer] — null or the scope itself.
|
||||
/// This is the core entity that provides access to the [ChildScopeContainer].
|
||||
///
|
||||
/// In order to create [BaseChildScopeHolder]
|
||||
/// you have to provider a Parent [BaseScopeContainer].
|
||||
///
|
||||
/// This holder allows to keep a [BaseScopeContainer] as an abstract interface [Scope].
|
||||
/// Your [BaseScopeContainer] have to implement this [Scope] interface.
|
||||
///
|
||||
/// You can use [ChildScopeHolder] if you don't need
|
||||
/// to differentiate [BaseScopeContainer] and it's interface.
|
||||
abstract class BaseChildScopeHolder<
|
||||
ScopeType,
|
||||
Container extends ChildScopeContainer<Parent>,
|
||||
Parent extends Scope> extends CoreScopeHolder<ScopeType, Container>
|
||||
with _BaseChildScopeHolderMixin<ScopeType, Container, Parent> {
|
||||
BaseChildScopeHolder(
|
||||
Parent parent, {
|
||||
@Deprecated('Use scopeObservers instead')
|
||||
List<ScopeListener>? scopeListeners,
|
||||
@Deprecated('Use depObservers instead') List<DepListener>? depListeners,
|
||||
@Deprecated('Use asyncDepObservers instead')
|
||||
List<AsyncDepListener>? asyncDepListeners,
|
||||
List<ScopeObserver>? scopeObservers,
|
||||
List<DepObserver>? depObservers,
|
||||
List<AsyncDepObserver>? asyncDepObservers,
|
||||
}) : super(
|
||||
scopeObservers: scopeObservers,
|
||||
depObservers: depObservers,
|
||||
asyncDepObservers: asyncDepObservers,
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
scopeListeners: scopeListeners,
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
depListeners: depListeners,
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
asyncDepListeners: asyncDepListeners,
|
||||
) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
@protected
|
||||
Container createContainer(Parent parent);
|
||||
|
||||
Future<void> create() => init(createContainer(parent));
|
||||
}
|
||||
|
||||
/// Holder contains the state of a [DataScopeContainer] — null or the scope itself.
|
||||
/// This is the core entity that provides access to the [DataScopeContainer].
|
||||
///
|
||||
/// This holder allows to keep a [BaseScopeContainer] as an abstract interface [Scope].
|
||||
/// Your [BaseScopeContainer] have to implement this [Scope] interface.
|
||||
///
|
||||
/// You can use [DataScopeHolder] if you don't need
|
||||
/// to differentiate [BaseScopeContainer] and it's interface.
|
||||
abstract class BaseDataScopeHolder<
|
||||
Scope,
|
||||
Container extends DataScopeContainer<Data>,
|
||||
Data extends Object> extends CoreScopeHolder<Scope, Container>
|
||||
with _BaseDataScopeHolderMixin<Scope, Container, Data> {
|
||||
BaseDataScopeHolder({
|
||||
@Deprecated('Use scopeObservers instead')
|
||||
List<ScopeListener>? scopeListeners,
|
||||
@Deprecated('Use depObservers instead') List<DepListener>? depListeners,
|
||||
@Deprecated('Use asyncDepObservers instead')
|
||||
List<AsyncDepListener>? asyncDepListeners,
|
||||
List<ScopeObserver>? scopeObservers,
|
||||
List<DepObserver>? depObservers,
|
||||
List<AsyncDepObserver>? asyncDepObservers,
|
||||
}) : super(
|
||||
scopeObservers: scopeObservers,
|
||||
depObservers: depObservers,
|
||||
asyncDepObservers: asyncDepObservers,
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
scopeListeners: scopeListeners,
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
depListeners: depListeners,
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
asyncDepListeners: asyncDepListeners,
|
||||
);
|
||||
|
||||
@protected
|
||||
Container createContainer(Data data);
|
||||
|
||||
Future<void> create(Data data) => init(createContainer(data));
|
||||
}
|
||||
|
||||
/// Holder contains the state of a [ChildDataScopeContainer] — null or the scope itself.
|
||||
/// This is the core entity that provides access to the [ChildDataScopeContainer].
|
||||
///
|
||||
/// In order to create [BaseChildDataScopeHolder]
|
||||
/// you have to provider a Parent [BaseScopeContainer].
|
||||
///
|
||||
/// This holder allows to keep a [BaseScopeContainer] as an abstract interface [Scope].
|
||||
/// Your [BaseScopeContainer] have to implement this [Scope] interface.
|
||||
///
|
||||
/// You can use [ChildDataScopeHolder] if you don't need
|
||||
/// to differentiate [BaseScopeContainer] and it's interface.
|
||||
abstract class BaseChildDataScopeHolder<
|
||||
ScopeType,
|
||||
Container extends ChildDataScopeContainer<Parent, Data>,
|
||||
Parent extends Scope,
|
||||
Data extends Object> extends CoreScopeHolder<ScopeType, Container>
|
||||
with
|
||||
_BaseChildScopeHolderMixin<ScopeType, Container, Parent>,
|
||||
_BaseDataScopeHolderMixin<ScopeType, Container, Data> {
|
||||
BaseChildDataScopeHolder(
|
||||
Parent parent, {
|
||||
@Deprecated('Use scopeObservers instead')
|
||||
List<ScopeListener>? scopeListeners,
|
||||
@Deprecated('Use depObservers instead') List<DepListener>? depListeners,
|
||||
@Deprecated('Use asyncDepObservers instead')
|
||||
List<AsyncDepListener>? asyncDepListeners,
|
||||
List<ScopeObserver>? scopeObservers,
|
||||
List<DepObserver>? depObservers,
|
||||
List<AsyncDepObserver>? asyncDepObservers,
|
||||
}) : super(
|
||||
scopeObservers: scopeObservers,
|
||||
depObservers: depObservers,
|
||||
asyncDepObservers: asyncDepObservers,
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
scopeListeners: scopeListeners,
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
depListeners: depListeners,
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
asyncDepListeners: asyncDepListeners,
|
||||
) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
@protected
|
||||
Container createContainer(Parent parent, Data data);
|
||||
|
||||
Future<void> create(Data data) => init(createContainer(parent, data));
|
||||
}
|
||||
|
||||
mixin _BaseChildScopeHolderMixin<
|
||||
ScopeType,
|
||||
Container extends ChildScopeContainerMixin<Parent>,
|
||||
Parent extends Scope> on CoreScopeHolder<ScopeType, Container> {
|
||||
Parent? _parent;
|
||||
|
||||
/// Must not be used anyone except for the child with this mixin.
|
||||
/// Should only be used inside constructor, see [ChildScopeContainer].
|
||||
@protected
|
||||
set parent(Parent parent) => _parent = parent;
|
||||
|
||||
/// Access parent of the current [BaseScopeContainer].
|
||||
Parent get parent {
|
||||
final p = _parent;
|
||||
if (p == null) {
|
||||
throw ScopeException(
|
||||
'$runtimeType: You must set parent in your ScopeHolder constructor',
|
||||
);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
_AsyncVoidCallbackRemove? _parentRemoveListener;
|
||||
|
||||
@protected
|
||||
@override
|
||||
Future<void> init(Container scope) async {
|
||||
await super.init(scope);
|
||||
_parentRemoveListener = parent._listenDispose(_onParentDropped);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> drop() async {
|
||||
_parentRemoveListener?.call();
|
||||
_parentRemoveListener = null;
|
||||
await super.drop();
|
||||
}
|
||||
|
||||
Future<void> _onParentDropped() async {
|
||||
/// When parent is dropped, we are sure
|
||||
/// that current child scope won't be (and should no be) used again.
|
||||
/// It's not true for dropping only the current scope,
|
||||
/// that's why it's not in just drop method.
|
||||
_listeners.clear();
|
||||
await drop();
|
||||
}
|
||||
}
|
||||
|
||||
mixin _BaseDataScopeHolderMixin<
|
||||
Scope,
|
||||
Container extends DataScopeContainerMixin<Data>,
|
||||
Data extends Object> on CoreScopeHolder<Scope, Container> {}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
part of 'base_scope_container.dart';
|
||||
|
||||
/// This class helps to decompose [ScopeContainer] into a number of features.
|
||||
///
|
||||
/// For example we want to extract all monitoring entities into a separate feature:
|
||||
///
|
||||
/// class SomeScopeContainer extends ScopeContainer {
|
||||
/// // We declare [ScopeModule] inside it's [ScopeContainer]
|
||||
/// late final monitorScopeModule = MonitorScopeModule(this);
|
||||
///
|
||||
/// late final appManagerDep = dep(() => AppManager());
|
||||
///
|
||||
/// late final navigationDep = dep(() => Navigation());
|
||||
/// }
|
||||
///
|
||||
/// class MonitorScopeModule extends ScopeModule<SomeScopeContainer> {
|
||||
/// MonitorScopeModule(super.container);
|
||||
///
|
||||
/// late final reporterDep = dep(() => Reporter());
|
||||
///
|
||||
/// late final loggerDep = dep(() => Logger());
|
||||
/// }
|
||||
abstract class ScopeModule<Container extends BaseScopeContainer> {
|
||||
final Container container;
|
||||
|
||||
const ScopeModule(this.container);
|
||||
|
||||
@protected
|
||||
Dep<Value> dep<Value>(
|
||||
DepBuilder<Value> builder, {
|
||||
String? name,
|
||||
}) =>
|
||||
container.dep(builder, name: name);
|
||||
|
||||
@protected
|
||||
AsyncDep<Value> asyncDep<Value extends AsyncLifecycle>(
|
||||
DepBuilder<Value> builder, {
|
||||
String? name,
|
||||
}) =>
|
||||
container.rawAsyncDep(
|
||||
builder,
|
||||
init: (value) => value.init(),
|
||||
dispose: (value) => value.dispose(),
|
||||
name: name,
|
||||
);
|
||||
|
||||
@protected
|
||||
AsyncDep<Value> rawAsyncDep<Value>(
|
||||
DepBuilder<Value> builder, {
|
||||
required AsyncDepCallback<Value> init,
|
||||
required AsyncDepCallback<Value> dispose,
|
||||
String? name,
|
||||
}) =>
|
||||
container.rawAsyncDep(
|
||||
builder,
|
||||
init: init,
|
||||
dispose: dispose,
|
||||
name: name,
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'base_scope_container.dart';
|
||||
import 'core/scope_state.dart';
|
||||
|
||||
mixin ScopeStateStreamable<Scope> on ScopeStateHolder<Scope> {
|
||||
Stream<Scope?> get stream {
|
||||
late StreamController<Scope?> controller;
|
||||
late RemoveStateListener removeStateListener;
|
||||
|
||||
void onListen() {
|
||||
removeStateListener = listen((scope) {
|
||||
controller.add(scope);
|
||||
});
|
||||
}
|
||||
|
||||
void onCancel() async {
|
||||
removeStateListener();
|
||||
await controller.close();
|
||||
}
|
||||
|
||||
controller = StreamController<Scope?>(
|
||||
onListen: onListen,
|
||||
onCancel: onCancel,
|
||||
);
|
||||
|
||||
return controller.stream;
|
||||
}
|
||||
|
||||
Stream<ScopeState<Scope>> get stateStream {
|
||||
late StreamController<ScopeState<Scope>> controller;
|
||||
late RemoveStateListener removeStateListener;
|
||||
|
||||
void onListen() {
|
||||
removeStateListener = listenState((state) {
|
||||
controller.add(state);
|
||||
});
|
||||
}
|
||||
|
||||
void onCancel() async {
|
||||
removeStateListener();
|
||||
await controller.close();
|
||||
}
|
||||
|
||||
controller = StreamController<ScopeState<Scope>>(
|
||||
onListen: onListen,
|
||||
onCancel: onCancel,
|
||||
);
|
||||
|
||||
return controller.stream;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
part of '../base_scope_container.dart';
|
||||
|
||||
class ScopeContainerTestUtils {
|
||||
/// Count of mounted providers. After dispose provider will be unmount.
|
||||
@visibleForTesting
|
||||
static int getDepCount(BaseScopeContainer container) =>
|
||||
container._container.length;
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
part of '../base_scope_container.dart';
|
||||
|
||||
/// The class that has access to private fields of the given [ScopeStateHolder]
|
||||
@visibleForTesting
|
||||
class TestableScopeStateHolder<Scope> {
|
||||
final ScopeStateHolder<Scope> holder;
|
||||
|
||||
@visibleForTesting
|
||||
const TestableScopeStateHolder(this.holder);
|
||||
|
||||
LinkedList<Entry<Scope>> get listeners => holder._listeners;
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
library yx_scope;
|
||||
|
||||
export 'src/base_scope_container.dart'
|
||||
hide
|
||||
ScopeObserverInternal,
|
||||
DepObserverInternal,
|
||||
AsyncDepObserverInternal,
|
||||
Entry,
|
||||
TestableScopeStateHolder,
|
||||
CustomDep,
|
||||
CustomAsyncDep;
|
||||
export 'src/core/async_lifecycle.dart';
|
||||
export 'src/core/scope_exception.dart';
|
||||
export 'src/core/scope_state.dart';
|
||||
export 'src/monitoring/observers.dart';
|
||||
export 'src/monitoring/models/dep_id.dart';
|
||||
export 'src/monitoring/models/scope_id.dart';
|
||||
export 'src/monitoring/models/value_meta.dart';
|
||||
export 'src/monitoring/scope_observatory.dart';
|
||||
export 'src/scope_container.dart';
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
name: yx_scope
|
||||
description: A core package of the compile-safe DI framework with advanced scoping capabilities
|
||||
version: 1.1.3
|
||||
repository: https://github.com/yandex/city-services-pub/tree/main/yx_scope/packages/yx_scope
|
||||
issue_tracker: https://github.com/yandex/city-services-pub/issues
|
||||
topics:
|
||||
- di
|
||||
- scope
|
||||
- dependency-injection
|
||||
- dependency-management
|
||||
|
||||
environment:
|
||||
sdk: ">=2.19.0 <4.0.0"
|
||||
|
||||
dependencies:
|
||||
meta: ^1.8.0
|
||||
|
||||
dev_dependencies:
|
||||
test: ^1.16.0
|
||||
lints: ^2.0.1
|
||||
|
||||
screenshots:
|
||||
- description: The yx_scope package logo
|
||||
path: screenshots/yx_scope_logo.webp
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 63 KiB |
|
|
@ -0,0 +1,349 @@
|
|||
import 'package:test/test.dart';
|
||||
import 'package:yx_scope/advanced.dart';
|
||||
import 'package:yx_scope/src/monitoring/raw_observers.dart';
|
||||
import 'package:yx_scope/yx_scope.dart';
|
||||
|
||||
import 'utils/test_logger.dart';
|
||||
import 'utils/utils.dart';
|
||||
|
||||
void main() {
|
||||
final observer = _TestObserver();
|
||||
|
||||
setUp(() {
|
||||
_TestDep.instances.clear();
|
||||
_TestAsyncDepNoAsyncLifecycle.instances.clear();
|
||||
_TestAsyncDep.instances.clear();
|
||||
ScopeObservatory.logger = const TestLogger();
|
||||
|
||||
RawScopeObserver.override = observer;
|
||||
RawDepObserver.override = observer;
|
||||
RawAsyncDepObserver.override = observer;
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
observer.scopeDeps.clear();
|
||||
RawScopeObserver.override = null;
|
||||
RawDepObserver.override = null;
|
||||
RawAsyncDepObserver.override = null;
|
||||
});
|
||||
|
||||
group('sync dependencies', () {
|
||||
test('dep is created lazily only when dep.get is called', () {
|
||||
final scope = _TestScope();
|
||||
expect(_TestDep.instances.isEmpty, isTrue);
|
||||
|
||||
final dep = scope.myDep;
|
||||
expect(_TestDep.instances.isEmpty, isTrue);
|
||||
|
||||
final myDep = dep.get;
|
||||
expect(_TestDep.instances.length, 1);
|
||||
// dep.get returns the same instance as was created
|
||||
expect(_TestDep.instances.first, myDep);
|
||||
});
|
||||
|
||||
test('dep creates only one instance', () {
|
||||
final scope = _TestScope();
|
||||
expect(scope.myDep.get, scope.myDep.get);
|
||||
});
|
||||
|
||||
test('dep is registered in observer lazily', () {
|
||||
final scope = _TestScope();
|
||||
expect(observer.scopeDeps['_TestScope'], isNull);
|
||||
|
||||
final dep = scope.myDep.get;
|
||||
expect(
|
||||
observer.scopeDeps['_TestScope']?.first,
|
||||
dep.runtimeType.toString(),
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'dependencies with the same type but different names'
|
||||
' is registered separately in observer', () {
|
||||
final scope = _TestScope();
|
||||
expect(observer.scopeDeps['_TestScope'], isNull);
|
||||
|
||||
final dep1 = scope.myDep.get;
|
||||
expect(
|
||||
observer.scopeContainsDep('_TestScope', dep1.runtimeType.toString()),
|
||||
isTrue,
|
||||
);
|
||||
|
||||
final dep2 = scope.myDuplicatedDep.get;
|
||||
expect(
|
||||
observer.scopeContainsDep('_TestScope', dep2.runtimeType.toString()),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
|
||||
test('dep returns non-nullable value', () {
|
||||
final scope = _TestScope();
|
||||
expect(observer.scopeDeps['_TestScope'], isNull);
|
||||
|
||||
final dep = scope.myNullableDep.get;
|
||||
expect(
|
||||
dep,
|
||||
isNotNull,
|
||||
);
|
||||
expect(
|
||||
observer.scopeDeps['_TestScope']?.first
|
||||
.startsWith(dep.runtimeType.toString()),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
|
||||
test('dep returns null value', () {
|
||||
final scope = _TestScope(isNullableDep: true);
|
||||
expect(observer.scopeDeps['_TestScope'], isNull);
|
||||
|
||||
final dep = scope.myNullableDep.get;
|
||||
expect(
|
||||
dep,
|
||||
isNull,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('async dependencies', () {
|
||||
test(
|
||||
'async dep initialized lazily on scope init and disposed on scope dispose',
|
||||
() async {
|
||||
final scopeHolder = _TestAsyncDepScopeHolder();
|
||||
expect(_TestAsyncDepNoAsyncLifecycle.instances.isEmpty, isTrue);
|
||||
expect(_TestAsyncDep.instances.isEmpty, isTrue);
|
||||
|
||||
await scopeHolder.create();
|
||||
final scope = scopeHolder.scope;
|
||||
if (scope == null) {
|
||||
throw Exception('Scope must be no-null here');
|
||||
}
|
||||
|
||||
final asyncDep = scope.myAsyncDep;
|
||||
// no need to call dep.get, because it will be called during scope initialization
|
||||
expect(_TestAsyncDepNoAsyncLifecycle.instances.length, 1);
|
||||
|
||||
final secondAsyncDep = scope.mySecondAsyncDep;
|
||||
// no need to call dep.get, because it will be called during scope initialization
|
||||
expect(_TestAsyncDep.instances.length, 1);
|
||||
|
||||
final depInstance = asyncDep.get;
|
||||
expect(_TestAsyncDepNoAsyncLifecycle.instances.first, depInstance);
|
||||
expect(depInstance._loaded, isTrue);
|
||||
|
||||
final secondDepInstance = secondAsyncDep.get;
|
||||
expect(_TestAsyncDep.instances.first, secondDepInstance);
|
||||
expect(secondDepInstance._initialized, isTrue);
|
||||
|
||||
// async dep creates only one instance
|
||||
expect(depInstance, asyncDep.get);
|
||||
expect(asyncDep.get, asyncDep.get);
|
||||
expect(secondDepInstance, secondAsyncDep.get);
|
||||
expect(secondAsyncDep.get, secondAsyncDep.get);
|
||||
|
||||
await scopeHolder.drop();
|
||||
|
||||
expect(depInstance._loaded, isFalse);
|
||||
expect(secondDepInstance._initialized, isFalse);
|
||||
});
|
||||
|
||||
test('uninitialized async dep throws an assertion when calling dep.get',
|
||||
() async {
|
||||
final scopeHolder = _TestAsyncDepScopeHolder();
|
||||
expect(_TestAsyncDep.instances.isEmpty, isTrue);
|
||||
|
||||
await scopeHolder.create();
|
||||
final scope = scopeHolder.scope;
|
||||
if (scope == null) {
|
||||
throw Exception('Scope must be no-null here');
|
||||
}
|
||||
|
||||
final uninitializedAsyncDep = scope.uninitializedAsyncDep;
|
||||
// We have 2 _TestAsyncDep deps in the scope
|
||||
// (mySecondAsyncDep and uninitializedAsyncDep),
|
||||
// but we forgotten to initialize this dep,
|
||||
// so there is only one instance after scope creation.
|
||||
expect(_TestAsyncDep.instances.length, 1);
|
||||
|
||||
await expectAssertion(() => uninitializedAsyncDep.get);
|
||||
|
||||
await scopeHolder.drop();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class _TestScope extends ScopeContainer {
|
||||
final bool isNullableDep;
|
||||
|
||||
_TestScope({this.isNullableDep = false});
|
||||
|
||||
late final myDep = dep(() => _TestDep());
|
||||
|
||||
late final myDuplicatedDep = dep(() => _TestDep(), name: 'duplicate');
|
||||
|
||||
late final myNullableDep = dep(() => isNullableDep ? null : _TestDep());
|
||||
}
|
||||
|
||||
class _TestDep {
|
||||
static final instances = <_TestDep>[];
|
||||
|
||||
_TestDep() {
|
||||
instances.add(this);
|
||||
}
|
||||
}
|
||||
|
||||
class _TestAsyncDepNoAsyncLifecycle {
|
||||
static final instances = <_TestAsyncDepNoAsyncLifecycle>[];
|
||||
|
||||
_TestAsyncDepNoAsyncLifecycle() {
|
||||
instances.add(this);
|
||||
}
|
||||
|
||||
var _loaded = false;
|
||||
|
||||
Future<void> load() async {
|
||||
_loaded = true;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
_loaded = false;
|
||||
}
|
||||
}
|
||||
|
||||
class _TestAsyncDep implements AsyncLifecycle {
|
||||
static final instances = <_TestAsyncDep>[];
|
||||
|
||||
_TestAsyncDep() {
|
||||
instances.add(this);
|
||||
}
|
||||
|
||||
var _initialized = false;
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
_initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
class _TestAsyncDepScope extends ScopeContainer {
|
||||
@override
|
||||
List<Set<AsyncDep>> get initializeQueue => [
|
||||
{myAsyncDep, mySecondAsyncDep}
|
||||
];
|
||||
|
||||
late final myAsyncDep = rawAsyncDep(
|
||||
() => _TestAsyncDepNoAsyncLifecycle(),
|
||||
init: (dep) async => dep.load(),
|
||||
dispose: (dep) async => dep.clear(),
|
||||
);
|
||||
|
||||
late final mySecondAsyncDep = asyncDep(() => _TestAsyncDep());
|
||||
|
||||
late final uninitializedAsyncDep = asyncDep(
|
||||
() => _TestAsyncDep(),
|
||||
name: 'uninitialized',
|
||||
);
|
||||
}
|
||||
|
||||
class _TestAsyncDepScopeHolder extends ScopeHolder<_TestAsyncDepScope> {
|
||||
@override
|
||||
_TestAsyncDepScope createContainer() => _TestAsyncDepScope();
|
||||
}
|
||||
|
||||
class _TestObserver implements RawScopeObserver, RawAsyncDepObserver {
|
||||
final scopeDeps = <String, Set<String>>{};
|
||||
|
||||
_TestObserver();
|
||||
|
||||
@override
|
||||
void onDepDisposeFailed(
|
||||
BaseScopeContainer scope,
|
||||
Dep<dynamic> dep,
|
||||
Object exception,
|
||||
StackTrace stackTrace,
|
||||
) {}
|
||||
|
||||
@override
|
||||
void onDepDisposed(BaseScopeContainer scope, Dep<dynamic> dep) {}
|
||||
|
||||
@override
|
||||
void onDepInitializeFailed(
|
||||
BaseScopeContainer scope,
|
||||
Dep<dynamic> dep,
|
||||
Object exception,
|
||||
StackTrace stackTrace,
|
||||
) {}
|
||||
|
||||
@override
|
||||
void onDepInitialized(BaseScopeContainer scope, Dep<dynamic> dep) {}
|
||||
|
||||
@override
|
||||
void onDepStartDispose(BaseScopeContainer scope, Dep<dynamic> dep) {}
|
||||
|
||||
@override
|
||||
void onDepStartInitialize(BaseScopeContainer scope, Dep<dynamic> dep) {}
|
||||
|
||||
@override
|
||||
void onScopeDisposeDepFailed(
|
||||
BaseScopeContainer scope,
|
||||
Dep<dynamic> dep,
|
||||
Object exception,
|
||||
StackTrace stackTrace,
|
||||
) {}
|
||||
|
||||
@override
|
||||
void onScopeDisposed(BaseScopeContainer scope) =>
|
||||
scopeDeps.remove(ScopeMeta(scope).id.type.toString());
|
||||
|
||||
@override
|
||||
void onScopeInitializeFailed(
|
||||
BaseScopeContainer scope,
|
||||
Object exception,
|
||||
StackTrace stackTrace,
|
||||
) {}
|
||||
|
||||
@override
|
||||
void onScopeInitialized(BaseScopeContainer scope) {}
|
||||
|
||||
@override
|
||||
void onScopeStartDispose(BaseScopeContainer scope) {}
|
||||
|
||||
@override
|
||||
void onScopeStartInitialize(BaseScopeContainer scope) {
|
||||
scopeDeps[ScopeMeta(scope).id.type.toString()] = {};
|
||||
}
|
||||
|
||||
@override
|
||||
void onValueCreateFailed(
|
||||
BaseScopeContainer scope,
|
||||
Dep<dynamic> dep,
|
||||
Object exception,
|
||||
StackTrace stackTrace,
|
||||
) {}
|
||||
|
||||
@override
|
||||
void onValueStartCreate(BaseScopeContainer scope, Dep<dynamic> dep) {}
|
||||
|
||||
@override
|
||||
void onValueCreated(
|
||||
BaseScopeContainer scope, Dep<dynamic> dep, Object? value) {
|
||||
final scopeMeta = ScopeMeta(scope);
|
||||
final depMeta = DepMeta(dep);
|
||||
final deps = scopeDeps[scopeMeta.id.type.toString()] ?? <String>{};
|
||||
scopeDeps[scopeMeta.id.type.toString()] = deps;
|
||||
deps.add(depMeta.id.valueType.toString());
|
||||
}
|
||||
|
||||
@override
|
||||
void onValueCleared(
|
||||
BaseScopeContainer scope, Dep<dynamic> dep, Object? value) =>
|
||||
scopeDeps[ScopeMeta(scope).id.type.toString()]!
|
||||
.remove(DepMeta(dep).id.valueType.toString());
|
||||
|
||||
bool scopeContainsDep(String scopeType, String depType) =>
|
||||
scopeDeps[scopeType]?.contains(depType) ?? false;
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
import 'package:test/test.dart';
|
||||
import 'package:yx_scope/yx_scope.dart';
|
||||
|
||||
class AppScopeContainer extends ScopeContainer {}
|
||||
|
||||
class AppScopeHolder extends ScopeHolder<AppScopeContainer> {
|
||||
AppScopeHolder({
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
List<ScopeListener>? scopeListeners,
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
List<DepListener>? depListeners,
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
List<AsyncDepListener>? asyncDepListeners,
|
||||
List<ScopeObserver>? scopeObservers,
|
||||
List<DepObserver>? depObservers,
|
||||
List<AsyncDepObserver>? asyncDepObservers,
|
||||
}) : super(
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
scopeListeners: scopeListeners,
|
||||
scopeObservers: scopeObservers,
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
depListeners: depListeners,
|
||||
depObservers: depObservers,
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
asyncDepListeners: asyncDepListeners,
|
||||
asyncDepObservers: asyncDepObservers,
|
||||
);
|
||||
@override
|
||||
AppScopeContainer createContainer() => AppScopeContainer();
|
||||
}
|
||||
|
||||
void main() {
|
||||
test(
|
||||
'Fail assert if both scopeListeners and scopeObservers passed to ScopeHolder',
|
||||
() {
|
||||
expect(
|
||||
() => AppScopeHolder(scopeListeners: [], scopeObservers: []),
|
||||
throwsA(
|
||||
isA<AssertionError>(),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'Fail assert if both depListeners and depObservers passed to ScopeHolder',
|
||||
() {
|
||||
expect(
|
||||
() => AppScopeHolder(depListeners: [], depObservers: []),
|
||||
throwsA(
|
||||
isA<AssertionError>(),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'Fail assert if both asyncDepListeners and asyncDepObservers passed to ScopeHolder',
|
||||
() {
|
||||
expect(
|
||||
() => AppScopeHolder(asyncDepListeners: [], asyncDepObservers: []),
|
||||
throwsA(
|
||||
isA<AssertionError>(),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,738 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:test/test.dart';
|
||||
import 'package:yx_scope/yx_scope.dart';
|
||||
|
||||
import 'utils/test_logger.dart';
|
||||
|
||||
void main() {
|
||||
setUp(() {
|
||||
ScopeObservatory.logger = const TestLogger();
|
||||
});
|
||||
|
||||
test('success calls in correct order with correct params', () async {
|
||||
final listener = TestObserver();
|
||||
final holder = _TestScopeHolder(listener);
|
||||
|
||||
await holder.create();
|
||||
|
||||
holder.scope?.syncDep.get;
|
||||
|
||||
await holder.drop();
|
||||
|
||||
final events = listener._events;
|
||||
expect(events[0].name, 'onScopeStartInitialize');
|
||||
expect(events[0].scope.type, _TestScopeContainer);
|
||||
|
||||
expect(events[1].name, 'onValueStartCreate');
|
||||
expect(events[1].scope.type, _TestScopeContainer);
|
||||
expect(events[1].dep?.valueType, _TestAsyncDep);
|
||||
expect(events[1].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[2].name, 'onValueCreated');
|
||||
expect(events[2].scope.type, _TestScopeContainer);
|
||||
expect(events[2].dep?.valueType, _TestAsyncDep);
|
||||
expect(events[2].dep?.depHashCode, events[1].dep?.depHashCode);
|
||||
expect(events[2].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[3].name, 'onDepStartInitialize');
|
||||
expect(events[3].scope.type, _TestScopeContainer);
|
||||
expect(events[3].dep?.valueType, _TestAsyncDep);
|
||||
expect(events[3].dep?.depHashCode, events[1].dep?.depHashCode);
|
||||
expect(events[3].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[4].name, 'onDepInitialized');
|
||||
expect(events[4].scope.type, _TestScopeContainer);
|
||||
expect(events[4].dep?.valueType, _TestAsyncDep);
|
||||
expect(events[4].dep?.depHashCode, events[1].dep?.depHashCode);
|
||||
expect(events[4].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[5].name, 'onScopeInitialized');
|
||||
expect(events[5].scope.type, _TestScopeContainer);
|
||||
expect(events[5].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[6].name, 'onValueStartCreate');
|
||||
expect(events[6].scope.type, _TestScopeContainer);
|
||||
expect(events[6].dep?.valueType, _SyncDep);
|
||||
expect(events[6].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[7].name, 'onValueCreated');
|
||||
expect(events[7].scope.type, _TestScopeContainer);
|
||||
expect(events[7].dep?.valueType, _SyncDep);
|
||||
expect(events[7].dep?.depHashCode, events[6].dep?.depHashCode);
|
||||
expect(events[7].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[8].name, 'onScopeStartDispose');
|
||||
expect(events[8].scope.type, _TestScopeContainer);
|
||||
expect(events[8].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[9].name, 'onDepStartDispose');
|
||||
expect(events[9].scope.type, _TestScopeContainer);
|
||||
expect(events[9].dep?.valueType, _TestAsyncDep);
|
||||
expect(events[9].dep?.depHashCode, events[1].dep?.depHashCode);
|
||||
expect(events[9].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[10].name, 'onDepDisposed');
|
||||
expect(events[10].scope.type, _TestScopeContainer);
|
||||
expect(events[10].dep?.valueType, _TestAsyncDep);
|
||||
expect(events[10].dep?.depHashCode, events[1].dep?.depHashCode);
|
||||
expect(events[10].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[11].name, 'onValueCleared');
|
||||
expect(events[11].scope.type, _TestScopeContainer);
|
||||
expect(events[11].dep?.valueType, _SyncDep);
|
||||
expect(events[11].dep?.depHashCode, events[6].dep?.depHashCode);
|
||||
expect(events[11].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[12].name, 'onValueCleared');
|
||||
expect(events[12].scope.type, _TestScopeContainer);
|
||||
expect(events[12].dep?.valueType, _TestAsyncDep);
|
||||
expect(events[12].dep?.depHashCode, events[1].dep?.depHashCode);
|
||||
expect(events[12].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[13].name, 'onScopeDisposed');
|
||||
expect(events[13].scope.type, _TestScopeContainer);
|
||||
expect(events[13].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events.length, 14);
|
||||
});
|
||||
|
||||
test('init failure calls in correct order with correct params', () async {
|
||||
final listener = TestObserver();
|
||||
final holder = _BrokenAsyncDepScopeHolder(listener);
|
||||
|
||||
try {
|
||||
await holder.create();
|
||||
} catch (e) {
|
||||
expect(e, isA<BrokenInitException>());
|
||||
}
|
||||
|
||||
final events = listener._events;
|
||||
expect(events[0].name, 'onScopeStartInitialize');
|
||||
expect(events[0].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
|
||||
expect(events[1].name, 'onValueStartCreate');
|
||||
expect(events[1].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[1].dep?.valueType, _TestAsyncDep);
|
||||
expect(events[1].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[2].name, 'onValueCreated');
|
||||
expect(events[2].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[2].dep?.valueType, _TestAsyncDep);
|
||||
expect(events[2].dep?.depHashCode, events[1].dep?.depHashCode);
|
||||
expect(events[2].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[3].name, 'onDepStartInitialize');
|
||||
expect(events[3].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[3].dep?.valueType, _TestAsyncDep);
|
||||
expect(events[3].dep?.depHashCode, events[1].dep?.depHashCode);
|
||||
expect(events[3].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[4].name, 'onDepInitialized');
|
||||
expect(events[4].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[4].dep?.valueType, _TestAsyncDep);
|
||||
expect(events[4].dep?.depHashCode, events[1].dep?.depHashCode);
|
||||
expect(events[4].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[5].name, 'onValueStartCreate');
|
||||
expect(events[5].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[5].dep?.valueType, _BrokenInitAsyncDep);
|
||||
expect(events[5].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[6].name, 'onValueCreated');
|
||||
expect(events[6].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[6].dep?.valueType, _BrokenInitAsyncDep);
|
||||
expect(events[6].dep?.depHashCode, events[5].dep?.depHashCode);
|
||||
expect(events[6].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[7].name, 'onDepStartInitialize');
|
||||
expect(events[7].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[7].dep?.valueType, _BrokenInitAsyncDep);
|
||||
expect(events[7].dep?.depHashCode, events[5].dep?.depHashCode);
|
||||
expect(events[7].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[8].name, 'onDepInitializeFailed');
|
||||
expect(events[8].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[8].dep?.valueType, _BrokenInitAsyncDep);
|
||||
expect(events[8].dep?.depHashCode, events[5].dep?.depHashCode);
|
||||
expect(events[8].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
expect(events[8].exception, isA<BrokenInitException>());
|
||||
|
||||
expect(events[9].name, 'onScopeInitializeFailed');
|
||||
expect(events[9].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[9].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
expect(events[9].exception, isA<BrokenInitException>());
|
||||
|
||||
expect(events[10].name, 'onScopeStartDispose');
|
||||
expect(events[10].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[10].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[11].name, 'onDepStartDispose');
|
||||
expect(events[11].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[11].dep?.valueType, _TestAsyncDep);
|
||||
expect(events[11].dep?.depHashCode, events[1].dep?.depHashCode);
|
||||
expect(events[11].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[12].name, 'onDepDisposed');
|
||||
expect(events[12].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[12].dep?.valueType, _TestAsyncDep);
|
||||
expect(events[12].dep?.depHashCode, events[1].dep?.depHashCode);
|
||||
expect(events[12].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[13].name, 'onValueCleared');
|
||||
expect(events[13].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[13].dep?.valueType, _BrokenDisposeAsyncDep);
|
||||
expect(events[13].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[14].name, 'onValueCleared');
|
||||
expect(events[14].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[14].dep?.valueType, _BrokenInitAsyncDep);
|
||||
expect(events[14].dep?.depHashCode, events[5].dep?.depHashCode);
|
||||
expect(events[14].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[15].name, 'onValueCleared');
|
||||
expect(events[15].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[15].dep?.valueType, _TestAsyncDep);
|
||||
expect(events[15].dep?.depHashCode, events[1].dep?.depHashCode);
|
||||
expect(events[15].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[16].name, 'onScopeDisposed');
|
||||
expect(events[16].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[16].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events.length, 17);
|
||||
});
|
||||
|
||||
test('dispose calls in correct order with correct params', () async {
|
||||
final listener = TestObserver();
|
||||
final holder = _BrokenAsyncDepScopeHolder(listener, checkDispose: true);
|
||||
|
||||
await holder.create();
|
||||
try {
|
||||
await holder.drop();
|
||||
} catch (_) {
|
||||
fail('Drop must complete anyway');
|
||||
}
|
||||
|
||||
final events = listener._events;
|
||||
expect(events[0].name, 'onScopeStartInitialize');
|
||||
expect(events[0].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
|
||||
expect(events[1].name, 'onValueStartCreate');
|
||||
expect(events[1].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[1].dep?.valueType, _TestAsyncDep);
|
||||
expect(events[1].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[2].name, 'onValueCreated');
|
||||
expect(events[2].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[2].dep?.valueType, _TestAsyncDep);
|
||||
expect(events[2].dep?.depHashCode, events[1].dep?.depHashCode);
|
||||
expect(events[2].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[3].name, 'onDepStartInitialize');
|
||||
expect(events[3].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[3].dep?.valueType, _TestAsyncDep);
|
||||
expect(events[3].dep?.depHashCode, events[1].dep?.depHashCode);
|
||||
expect(events[3].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[4].name, 'onDepInitialized');
|
||||
expect(events[4].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[4].dep?.valueType, _TestAsyncDep);
|
||||
expect(events[4].dep?.depHashCode, events[1].dep?.depHashCode);
|
||||
expect(events[4].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[5].name, 'onValueStartCreate');
|
||||
expect(events[5].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[5].dep?.valueType, _BrokenDisposeAsyncDep);
|
||||
expect(events[5].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[6].name, 'onValueCreated');
|
||||
expect(events[6].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[6].dep?.valueType, _BrokenDisposeAsyncDep);
|
||||
expect(events[6].dep?.depHashCode, events[5].dep?.depHashCode);
|
||||
expect(events[6].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[7].name, 'onDepStartInitialize');
|
||||
expect(events[7].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[7].dep?.valueType, _BrokenDisposeAsyncDep);
|
||||
expect(events[7].dep?.depHashCode, events[5].dep?.depHashCode);
|
||||
expect(events[7].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[8].name, 'onDepInitialized');
|
||||
expect(events[8].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[8].dep?.valueType, _BrokenDisposeAsyncDep);
|
||||
expect(events[8].dep?.depHashCode, events[5].dep?.depHashCode);
|
||||
expect(events[8].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[9].name, 'onScopeInitialized');
|
||||
expect(events[9].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[9].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[10].name, 'onScopeStartDispose');
|
||||
expect(events[10].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[10].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[11].name, 'onDepStartDispose');
|
||||
expect(events[11].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[11].dep?.valueType, _BrokenDisposeAsyncDep);
|
||||
expect(events[11].dep?.depHashCode, events[5].dep?.depHashCode);
|
||||
expect(events[11].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[12].name, 'onDepDisposeFailed');
|
||||
expect(events[12].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[12].dep?.valueType, _BrokenDisposeAsyncDep);
|
||||
expect(events[12].dep?.depHashCode, events[5].dep?.depHashCode);
|
||||
expect(events[12].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
expect(events[12].exception, isA<BrokenDisposeException>());
|
||||
|
||||
expect(events[13].name, 'onScopeDisposeDepFailed');
|
||||
expect(events[13].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[13].dep?.valueType, _BrokenDisposeAsyncDep);
|
||||
expect(events[13].dep?.depHashCode, events[5].dep?.depHashCode);
|
||||
expect(events[13].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
expect(events[13].exception, isA<BrokenDisposeException>());
|
||||
|
||||
expect(events[14].name, 'onDepStartDispose');
|
||||
expect(events[14].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[14].dep?.valueType, _TestAsyncDep);
|
||||
expect(events[14].dep?.depHashCode, events[1].dep?.depHashCode);
|
||||
expect(events[14].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[15].name, 'onDepDisposed');
|
||||
expect(events[15].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[15].dep?.valueType, _TestAsyncDep);
|
||||
expect(events[15].dep?.depHashCode, events[1].dep?.depHashCode);
|
||||
expect(events[15].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[16].name, 'onValueCleared');
|
||||
expect(events[16].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[16].dep?.valueType, _BrokenDisposeAsyncDep);
|
||||
expect(events[16].dep?.depHashCode, events[5].dep?.depHashCode);
|
||||
expect(events[16].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[17].name, 'onValueCleared');
|
||||
expect(events[17].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[17].dep?.valueType, _TestAsyncDep);
|
||||
expect(events[17].dep?.depHashCode, events[1].dep?.depHashCode);
|
||||
expect(events[17].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[18].name, 'onScopeDisposed');
|
||||
expect(events[18].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[18].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events.length, 19);
|
||||
});
|
||||
|
||||
test('create failure calls in correct order with correct params', () async {
|
||||
final listener = TestObserver();
|
||||
final holder = _BrokenAsyncDepScopeHolder(listener, checkDispose: true);
|
||||
|
||||
await holder.create();
|
||||
|
||||
try {
|
||||
holder.scope?.brokenCreateDep.get;
|
||||
// should not produce any listener event
|
||||
holder.scope?.testAsyncDep.get;
|
||||
} catch (e) {
|
||||
expect(e, isA<BrokenCreateException>());
|
||||
}
|
||||
try {
|
||||
await holder.drop();
|
||||
} catch (_) {
|
||||
fail('Drop must complete anyway');
|
||||
}
|
||||
|
||||
final events = listener._events;
|
||||
expect(events[0].name, 'onScopeStartInitialize');
|
||||
expect(events[0].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
|
||||
expect(events[1].name, 'onValueStartCreate');
|
||||
expect(events[1].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[1].dep?.valueType, _TestAsyncDep);
|
||||
expect(events[1].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[2].name, 'onValueCreated');
|
||||
expect(events[2].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[2].dep?.valueType, _TestAsyncDep);
|
||||
expect(events[2].dep?.depHashCode, events[1].dep?.depHashCode);
|
||||
expect(events[2].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[3].name, 'onDepStartInitialize');
|
||||
expect(events[3].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[3].dep?.valueType, _TestAsyncDep);
|
||||
expect(events[3].dep?.depHashCode, events[1].dep?.depHashCode);
|
||||
expect(events[3].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[4].name, 'onDepInitialized');
|
||||
expect(events[4].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[4].dep?.valueType, _TestAsyncDep);
|
||||
expect(events[4].dep?.depHashCode, events[1].dep?.depHashCode);
|
||||
expect(events[4].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[5].name, 'onValueStartCreate');
|
||||
expect(events[5].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[5].dep?.valueType, _BrokenDisposeAsyncDep);
|
||||
expect(events[5].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[6].name, 'onValueCreated');
|
||||
expect(events[6].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[6].dep?.valueType, _BrokenDisposeAsyncDep);
|
||||
expect(events[6].dep?.depHashCode, events[5].dep?.depHashCode);
|
||||
expect(events[6].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[7].name, 'onDepStartInitialize');
|
||||
expect(events[7].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[7].dep?.valueType, _BrokenDisposeAsyncDep);
|
||||
expect(events[7].dep?.depHashCode, events[5].dep?.depHashCode);
|
||||
expect(events[7].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[8].name, 'onDepInitialized');
|
||||
expect(events[8].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[8].dep?.valueType, _BrokenDisposeAsyncDep);
|
||||
expect(events[8].dep?.depHashCode, events[5].dep?.depHashCode);
|
||||
expect(events[8].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[9].name, 'onScopeInitialized');
|
||||
expect(events[9].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[9].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[10].name, 'onValueStartCreate');
|
||||
expect(events[10].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[10].dep?.valueType, NeverCreatedValue);
|
||||
expect(events[10].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[11].name, 'onValueCreateFailed');
|
||||
expect(events[11].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[11].dep?.valueType, NeverCreatedValue);
|
||||
expect(events[11].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[12].name, 'onScopeStartDispose');
|
||||
expect(events[12].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[12].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[13].name, 'onDepStartDispose');
|
||||
expect(events[13].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[13].dep?.valueType, _BrokenDisposeAsyncDep);
|
||||
expect(events[13].dep?.depHashCode, events[5].dep?.depHashCode);
|
||||
expect(events[13].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[14].name, 'onDepDisposeFailed');
|
||||
expect(events[14].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[14].dep?.valueType, _BrokenDisposeAsyncDep);
|
||||
expect(events[14].dep?.depHashCode, events[5].dep?.depHashCode);
|
||||
expect(events[14].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
expect(events[14].exception, isA<BrokenDisposeException>());
|
||||
|
||||
expect(events[15].name, 'onScopeDisposeDepFailed');
|
||||
expect(events[15].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[15].dep?.valueType, _BrokenDisposeAsyncDep);
|
||||
expect(events[15].dep?.depHashCode, events[5].dep?.depHashCode);
|
||||
expect(events[15].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
expect(events[15].exception, isA<BrokenDisposeException>());
|
||||
|
||||
expect(events[16].name, 'onDepStartDispose');
|
||||
expect(events[16].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[16].dep?.valueType, _TestAsyncDep);
|
||||
expect(events[16].dep?.depHashCode, events[1].dep?.depHashCode);
|
||||
expect(events[16].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[17].name, 'onDepDisposed');
|
||||
expect(events[17].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[17].dep?.valueType, _TestAsyncDep);
|
||||
expect(events[17].dep?.depHashCode, events[1].dep?.depHashCode);
|
||||
expect(events[17].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[18].name, 'onValueCleared');
|
||||
expect(events[18].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[18].dep?.valueType, NeverCreatedValue);
|
||||
expect(events[18].dep?.depHashCode, events[10].dep?.depHashCode);
|
||||
expect(events[18].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[19].name, 'onValueCleared');
|
||||
expect(events[19].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[19].dep?.valueType, _BrokenDisposeAsyncDep);
|
||||
expect(events[19].dep?.depHashCode, events[5].dep?.depHashCode);
|
||||
expect(events[19].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[20].name, 'onValueCleared');
|
||||
expect(events[20].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[20].dep?.valueType, _TestAsyncDep);
|
||||
expect(events[20].dep?.depHashCode, events[1].dep?.depHashCode);
|
||||
expect(events[20].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events[21].name, 'onScopeDisposed');
|
||||
expect(events[21].scope.type, _BrokenAsyncDepScopeContainer);
|
||||
expect(events[21].scope.scopeHashCode, events[0].scope.scopeHashCode);
|
||||
|
||||
expect(events.length, 22);
|
||||
});
|
||||
}
|
||||
|
||||
class ObserverEvent {
|
||||
final String name;
|
||||
final ScopeId scope;
|
||||
final DepId? dep;
|
||||
final ValueMeta? value;
|
||||
final Object? exception;
|
||||
final StackTrace? stackTrace;
|
||||
|
||||
ObserverEvent({
|
||||
required this.name,
|
||||
required this.scope,
|
||||
this.dep,
|
||||
this.value,
|
||||
this.exception,
|
||||
this.stackTrace,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
'ObserverEvent{name: $name, scope: $scope, dep: $dep, value: $value, '
|
||||
'exception: $exception, stackTrace: $stackTrace}';
|
||||
}
|
||||
|
||||
class TestObserver implements ScopeObserver, DepObserver, AsyncDepObserver {
|
||||
final _events = <ObserverEvent>[];
|
||||
|
||||
TestObserver();
|
||||
|
||||
void _log(
|
||||
String name,
|
||||
ScopeId scope, {
|
||||
DepId? dep,
|
||||
ValueMeta? value,
|
||||
Object? exception,
|
||||
StackTrace? stackTrace,
|
||||
}) {
|
||||
print('$name: $scope, $dep, $value');
|
||||
if (exception != null) {
|
||||
print('$exception\n$stackTrace');
|
||||
}
|
||||
_events.add(
|
||||
ObserverEvent(
|
||||
name: name,
|
||||
scope: scope,
|
||||
dep: dep,
|
||||
value: value,
|
||||
exception: exception,
|
||||
stackTrace: stackTrace,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void onScopeStartInitialize(ScopeId scope) =>
|
||||
_log('onScopeStartInitialize', scope);
|
||||
|
||||
@override
|
||||
void onScopeInitialized(ScopeId scope) => _log('onScopeInitialized', scope);
|
||||
|
||||
@override
|
||||
void onScopeInitializeFailed(
|
||||
ScopeId scope,
|
||||
Object exception,
|
||||
StackTrace stackTrace,
|
||||
) =>
|
||||
_log(
|
||||
'onScopeInitializeFailed',
|
||||
scope,
|
||||
exception: exception,
|
||||
stackTrace: stackTrace,
|
||||
);
|
||||
|
||||
@override
|
||||
void onScopeStartDispose(ScopeId scope) => _log('onScopeStartDispose', scope);
|
||||
|
||||
@override
|
||||
void onScopeDisposed(ScopeId scope) => _log('onScopeDisposed', scope);
|
||||
|
||||
@override
|
||||
void onScopeDisposeDepFailed(
|
||||
ScopeId scope,
|
||||
DepId dep,
|
||||
Object exception,
|
||||
StackTrace stackTrace,
|
||||
) =>
|
||||
_log(
|
||||
'onScopeDisposeDepFailed',
|
||||
scope,
|
||||
dep: dep,
|
||||
exception: exception,
|
||||
stackTrace: stackTrace,
|
||||
);
|
||||
|
||||
@override
|
||||
void onValueCreated(ScopeId scope, DepId dep, ValueMeta? valueMeta) =>
|
||||
_log('onValueCreated', scope, dep: dep, value: valueMeta);
|
||||
|
||||
@override
|
||||
void onValueStartCreate(ScopeId scope, DepId dep) =>
|
||||
_log('onValueStartCreate', scope, dep: dep);
|
||||
|
||||
@override
|
||||
void onValueCreateFailed(
|
||||
ScopeId scope,
|
||||
DepId dep,
|
||||
Object exception,
|
||||
StackTrace stackTrace,
|
||||
) =>
|
||||
_log(
|
||||
'onValueCreateFailed',
|
||||
scope,
|
||||
dep: dep,
|
||||
exception: exception,
|
||||
stackTrace: stackTrace,
|
||||
);
|
||||
|
||||
@override
|
||||
void onValueCleared(ScopeId scope, DepId dep, ValueMeta? valueMeta) =>
|
||||
_log('onValueCleared', scope, dep: dep, value: valueMeta);
|
||||
|
||||
@override
|
||||
void onDepStartInitialize(ScopeId scope, DepId dep) =>
|
||||
_log('onDepStartInitialize', scope, dep: dep);
|
||||
|
||||
@override
|
||||
void onDepInitialized(ScopeId scope, DepId dep) =>
|
||||
_log('onDepInitialized', scope, dep: dep);
|
||||
|
||||
@override
|
||||
void onDepStartDispose(ScopeId scope, DepId dep) =>
|
||||
_log('onDepStartDispose', scope, dep: dep);
|
||||
|
||||
@override
|
||||
void onDepDisposed(ScopeId scope, DepId dep) =>
|
||||
_log('onDepDisposed', scope, dep: dep);
|
||||
|
||||
@override
|
||||
void onDepInitializeFailed(
|
||||
ScopeId scope,
|
||||
DepId dep,
|
||||
Object exception,
|
||||
StackTrace stackTrace,
|
||||
) =>
|
||||
_log(
|
||||
'onDepInitializeFailed',
|
||||
scope,
|
||||
dep: dep,
|
||||
exception: exception,
|
||||
stackTrace: stackTrace,
|
||||
);
|
||||
|
||||
@override
|
||||
void onDepDisposeFailed(
|
||||
ScopeId scope,
|
||||
DepId dep,
|
||||
Object exception,
|
||||
StackTrace stackTrace,
|
||||
) =>
|
||||
_log(
|
||||
'onDepDisposeFailed',
|
||||
scope,
|
||||
dep: dep,
|
||||
exception: exception,
|
||||
stackTrace: stackTrace,
|
||||
);
|
||||
}
|
||||
|
||||
class _TestScopeContainer extends ScopeContainer {
|
||||
@override
|
||||
List<Set<AsyncDep>> get initializeQueue => [
|
||||
{myAsyncDep}
|
||||
];
|
||||
|
||||
late final myAsyncDep = asyncDep(() => _TestAsyncDep());
|
||||
|
||||
late final syncDep = dep(() => _SyncDep());
|
||||
}
|
||||
|
||||
class _TestScopeHolder extends ScopeHolder<_TestScopeContainer> {
|
||||
_TestScopeHolder(TestObserver listener)
|
||||
: super(
|
||||
scopeObservers: [listener],
|
||||
depObservers: [listener],
|
||||
asyncDepObservers: [listener],
|
||||
);
|
||||
|
||||
@override
|
||||
_TestScopeContainer createContainer() => _TestScopeContainer();
|
||||
}
|
||||
|
||||
class _SyncDep {}
|
||||
|
||||
class _TestAsyncDep implements AsyncLifecycle {
|
||||
@override
|
||||
Future<void> init() async {}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {}
|
||||
}
|
||||
|
||||
class _BrokenAsyncDepScopeContainer extends ScopeContainer {
|
||||
final bool checkDispose;
|
||||
|
||||
_BrokenAsyncDepScopeContainer({required this.checkDispose});
|
||||
|
||||
@override
|
||||
List<Set<AsyncDep>> get initializeQueue => [
|
||||
{testAsyncDep},
|
||||
if (!checkDispose) {brokenInitDep},
|
||||
{brokenDisposeDep},
|
||||
];
|
||||
|
||||
late final testAsyncDep = asyncDep(() => _TestAsyncDep());
|
||||
|
||||
late final brokenInitDep = asyncDep(() => _BrokenInitAsyncDep());
|
||||
|
||||
late final brokenDisposeDep = asyncDep(() => _BrokenDisposeAsyncDep());
|
||||
|
||||
late final brokenCreateDep = dep(() => NeverCreatedValue());
|
||||
}
|
||||
|
||||
class _BrokenAsyncDepScopeHolder
|
||||
extends ScopeHolder<_BrokenAsyncDepScopeContainer> {
|
||||
final bool checkDispose;
|
||||
|
||||
_BrokenAsyncDepScopeHolder(TestObserver listener, {this.checkDispose = false})
|
||||
: super(
|
||||
scopeObservers: [listener],
|
||||
depObservers: [listener],
|
||||
asyncDepObservers: [listener],
|
||||
);
|
||||
|
||||
@override
|
||||
_BrokenAsyncDepScopeContainer createContainer() =>
|
||||
_BrokenAsyncDepScopeContainer(checkDispose: checkDispose);
|
||||
}
|
||||
|
||||
class _BrokenInitAsyncDep implements AsyncLifecycle {
|
||||
@override
|
||||
Future<void> init() async {
|
||||
throw BrokenInitException();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {}
|
||||
}
|
||||
|
||||
class _BrokenDisposeAsyncDep implements AsyncLifecycle {
|
||||
@override
|
||||
Future<void> init() async {}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
throw BrokenDisposeException();
|
||||
}
|
||||
}
|
||||
|
||||
class NeverCreatedValue {
|
||||
NeverCreatedValue() {
|
||||
throw BrokenCreateException();
|
||||
}
|
||||
}
|
||||
|
||||
class BrokenCreateException implements Exception {}
|
||||
|
||||
class BrokenInitException implements Exception {}
|
||||
|
||||
class BrokenDisposeException implements Exception {}
|
||||
|
|
@ -0,0 +1,265 @@
|
|||
import 'package:test/test.dart';
|
||||
import 'package:yx_scope/yx_scope.dart';
|
||||
|
||||
import 'utils/test_logger.dart';
|
||||
|
||||
void main() {
|
||||
setUp(() {
|
||||
ScopeObservatory.logger = const TestLogger();
|
||||
});
|
||||
|
||||
test('child scope is not created with the parent', () async {
|
||||
final parentHolder = _ParentScopeHolder();
|
||||
await parentHolder.create();
|
||||
final childHolder = parentHolder.scope?.childScopeDep.get;
|
||||
|
||||
expect(parentHolder.scope, isNotNull);
|
||||
expect(childHolder, isNotNull);
|
||||
expect(childHolder?.scope, isNull);
|
||||
});
|
||||
|
||||
test('child scope disposed when parent scope dropped', () async {
|
||||
final parentHolder = _ParentScopeHolder();
|
||||
await parentHolder.create();
|
||||
final childHolder = parentHolder.scope?.childScopeDep.get;
|
||||
|
||||
expect(parentHolder.scope, isNotNull);
|
||||
expect(childHolder, isNotNull);
|
||||
expect(childHolder?.scope, isNull);
|
||||
|
||||
final testData = _TestData();
|
||||
await childHolder?.create(testData);
|
||||
|
||||
expect(parentHolder.scope, isNotNull);
|
||||
expect(childHolder, isNotNull);
|
||||
expect(childHolder?.scope, isNotNull);
|
||||
|
||||
final actualData = childHolder?.scope?.data;
|
||||
final actualChildAsyncDep = childHolder?.scope?.childAsyncDep.get;
|
||||
expect(actualChildAsyncDep?._initialized, isTrue);
|
||||
|
||||
expect(actualData, testData);
|
||||
|
||||
await parentHolder.drop();
|
||||
|
||||
expect(parentHolder.scope, isNull);
|
||||
expect(childHolder?.scope, isNull);
|
||||
expect(actualChildAsyncDep?._initialized, isFalse);
|
||||
});
|
||||
|
||||
test('parent dep instances are the same as provided in child', () async {
|
||||
final parentHolder = _ParentScopeHolder();
|
||||
await parentHolder.create();
|
||||
final childHolder = parentHolder.scope?.childScopeDep.get;
|
||||
|
||||
expect(parentHolder.scope, isNotNull);
|
||||
expect(childHolder, isNotNull);
|
||||
expect(childHolder?.scope, isNull);
|
||||
|
||||
final testData = _TestData();
|
||||
await childHolder?.create(testData);
|
||||
|
||||
expect(parentHolder.scope, isNotNull);
|
||||
expect(childHolder, isNotNull);
|
||||
expect(childHolder?.scope, isNotNull);
|
||||
|
||||
// data is not created twice
|
||||
expect(
|
||||
parentHolder.scope?.childScopeDep.get.scope?.data,
|
||||
parentHolder.scope?.childScopeDep.get.scope?.data,
|
||||
);
|
||||
|
||||
expect(
|
||||
parentHolder.scope?.parentDep.get,
|
||||
parentHolder.scope?.childScopeDep.get.scope?.childDep.get.parentDep,
|
||||
);
|
||||
expect(
|
||||
parentHolder.scope?.parentAsyncDep.get,
|
||||
parentHolder.scope?.childScopeDep.get.scope?.childDep.get.parentAsyncDep,
|
||||
);
|
||||
expect(
|
||||
testData,
|
||||
parentHolder.scope?.childScopeDep.get.scope?.childDep.get.data,
|
||||
);
|
||||
expect(
|
||||
parentHolder.scope?.parentDep.get,
|
||||
parentHolder.scope?.childScopeDep.get.scope?.childAsyncDep.get.parentDep,
|
||||
);
|
||||
expect(
|
||||
parentHolder.scope?.parentAsyncDep.get,
|
||||
parentHolder
|
||||
.scope?.childScopeDep.get.scope?.childAsyncDep.get.parentAsyncDep,
|
||||
);
|
||||
|
||||
await childHolder?.drop();
|
||||
|
||||
expect(parentHolder.scope, isNotNull);
|
||||
expect(childHolder, isNotNull);
|
||||
expect(childHolder?.scope, isNull);
|
||||
});
|
||||
|
||||
test(
|
||||
'Two different scope trees can contain deps with the same type and its different instances',
|
||||
() async {
|
||||
final parentHolder1 = _ParentScopeHolder();
|
||||
final parentHolder2 = _ParentScopeHolder();
|
||||
|
||||
expect(
|
||||
parentHolder1.scope?.parentDep.get,
|
||||
parentHolder2.scope?.parentDep.get,
|
||||
);
|
||||
expect(
|
||||
parentHolder1.scope?.parentAsyncDep.get,
|
||||
parentHolder2.scope?.parentAsyncDep.get,
|
||||
);
|
||||
expect(
|
||||
parentHolder1.scope?.childScopeDep.get.scope?.childDep.get,
|
||||
parentHolder2.scope?.childScopeDep.get.scope?.childDep.get,
|
||||
);
|
||||
expect(
|
||||
parentHolder1.scope?.childScopeDep.get.scope?.childAsyncDep.get,
|
||||
parentHolder2.scope?.childScopeDep.get.scope?.childAsyncDep.get,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
class _ParentScopeContainer extends ScopeContainer {
|
||||
@override
|
||||
List<Set<AsyncDep>> get initializeQueue => [
|
||||
{parentAsyncDep}
|
||||
];
|
||||
|
||||
late final childScopeDep = dep(() => _ChildScopeHolder(this));
|
||||
|
||||
late final childWithDuplicationScopeDep =
|
||||
dep(() => _ChildWithDuplicationScopeHolder(this));
|
||||
|
||||
late final parentDep = dep(() => _ParentDep());
|
||||
|
||||
late final parentAsyncDep = asyncDep(() => _ParentAsyncDep());
|
||||
}
|
||||
|
||||
class _ParentScopeHolder extends ScopeHolder<_ParentScopeContainer> {
|
||||
@override
|
||||
_ParentScopeContainer createContainer() => _ParentScopeContainer();
|
||||
}
|
||||
|
||||
class _ChildScopeContainer
|
||||
extends ChildDataScopeContainer<_ParentScopeContainer, _TestData> {
|
||||
_ChildScopeContainer({
|
||||
required _ParentScopeContainer parent,
|
||||
required _TestData data,
|
||||
}) : super(parent: parent, data: data);
|
||||
|
||||
@override
|
||||
List<Set<AsyncDep>> get initializeQueue => [
|
||||
{childAsyncDep}
|
||||
];
|
||||
|
||||
late final childDep = dep(
|
||||
() => _ChildDep(
|
||||
data,
|
||||
parent.parentDep.get,
|
||||
parent.parentAsyncDep.get,
|
||||
),
|
||||
);
|
||||
|
||||
late final childAsyncDep = asyncDep(
|
||||
() => _ChildAsyncDep(
|
||||
parent.parentDep.get,
|
||||
parent.parentAsyncDep.get,
|
||||
childDep.get,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class _ChildScopeHolder extends ChildDataScopeHolder<_ChildScopeContainer,
|
||||
_ParentScopeContainer, _TestData> {
|
||||
_ChildScopeHolder(_ParentScopeContainer parent) : super(parent);
|
||||
|
||||
@override
|
||||
_ChildScopeContainer createContainer(
|
||||
_ParentScopeContainer parent,
|
||||
_TestData data,
|
||||
) =>
|
||||
_ChildScopeContainer(data: data, parent: parent);
|
||||
}
|
||||
|
||||
class _ChildWithDuplicationScopeContainer
|
||||
extends ChildScopeContainer<_ParentScopeContainer> {
|
||||
_ChildWithDuplicationScopeContainer({required _ParentScopeContainer parent})
|
||||
: super(parent: parent);
|
||||
|
||||
late final duplicatedParentDep = dep(() => _ParentDep());
|
||||
|
||||
late final duplicatedParentAsyncDep = asyncDep(() => _ParentAsyncDep());
|
||||
|
||||
late final duplicatedChildDep = dep(
|
||||
() => _ChildDep(
|
||||
_TestData(),
|
||||
parent.parentDep.get,
|
||||
parent.parentAsyncDep.get,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class _ChildWithDuplicationScopeHolder extends ChildScopeHolder<
|
||||
_ChildWithDuplicationScopeContainer, _ParentScopeContainer> {
|
||||
_ChildWithDuplicationScopeHolder(_ParentScopeContainer parent)
|
||||
: super(parent);
|
||||
|
||||
@override
|
||||
_ChildWithDuplicationScopeContainer createContainer(
|
||||
_ParentScopeContainer parent) =>
|
||||
_ChildWithDuplicationScopeContainer(parent: parent);
|
||||
}
|
||||
|
||||
class _ParentDep {}
|
||||
|
||||
class _ParentAsyncDep implements AsyncLifecycle {
|
||||
// ignore: unused_field
|
||||
var _initialized = false;
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
_initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
class _ChildDep {
|
||||
final _TestData data;
|
||||
final _ParentDep parentDep;
|
||||
final _ParentAsyncDep parentAsyncDep;
|
||||
|
||||
const _ChildDep(this.data, this.parentDep, this.parentAsyncDep);
|
||||
}
|
||||
|
||||
class _ChildAsyncDep implements AsyncLifecycle {
|
||||
final _ParentDep parentDep;
|
||||
final _ParentAsyncDep parentAsyncDep;
|
||||
final _ChildDep childDep;
|
||||
var _initialized = false;
|
||||
|
||||
_ChildAsyncDep(
|
||||
this.parentDep,
|
||||
this.parentAsyncDep,
|
||||
this.childDep,
|
||||
);
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
_initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
class _TestData {}
|
||||
|
|
@ -0,0 +1,229 @@
|
|||
import 'package:test/test.dart';
|
||||
import 'package:yx_scope/src/base_scope_container.dart';
|
||||
import 'package:yx_scope/yx_scope.dart';
|
||||
|
||||
import 'utils/test_logger.dart';
|
||||
|
||||
void main() {
|
||||
setUp(() {
|
||||
ScopeObservatory.logger = const TestLogger();
|
||||
});
|
||||
|
||||
group('stream of scope', () {
|
||||
test('stream for scope happy path', () async {
|
||||
final holder = _TestScopeHolder();
|
||||
|
||||
int counter = 0;
|
||||
holder.stream.listen((scope) {
|
||||
if (counter % 2 == 0) {
|
||||
expect(scope, isNotNull);
|
||||
} else {
|
||||
expect(scope, isNull);
|
||||
}
|
||||
});
|
||||
|
||||
await holder.create();
|
||||
counter++;
|
||||
await holder.drop();
|
||||
counter++;
|
||||
await holder.create();
|
||||
counter++;
|
||||
await holder.drop();
|
||||
counter++;
|
||||
});
|
||||
|
||||
test('after pausing stream of scope it emits buffered events', () async {
|
||||
final holder = _TestScopeHolder();
|
||||
|
||||
int counter = 0;
|
||||
final subscription = holder.stream.listen((scope) {
|
||||
if (counter % 2 == 0) {
|
||||
expect(scope, isNotNull);
|
||||
} else {
|
||||
expect(scope, isNull);
|
||||
}
|
||||
counter++;
|
||||
});
|
||||
|
||||
await holder.create();
|
||||
await holder.drop();
|
||||
|
||||
subscription.pause();
|
||||
|
||||
await holder.create();
|
||||
await holder.drop();
|
||||
await holder.create();
|
||||
|
||||
subscription.resume();
|
||||
|
||||
await holder.drop();
|
||||
await holder.create();
|
||||
await holder.drop();
|
||||
|
||||
expect(counter, 7);
|
||||
});
|
||||
|
||||
test('reusing the same instance of stream cause an exception', () async {
|
||||
final holder = _TestScopeHolder();
|
||||
final stream = holder.stream;
|
||||
|
||||
stream.listen((_) {});
|
||||
try {
|
||||
stream.listen((_) {});
|
||||
fail('Must throw an exception');
|
||||
} on StateError catch (e) {
|
||||
expect(e.message, 'Stream has already been listened to.');
|
||||
}
|
||||
});
|
||||
|
||||
test('two different get calls return different streams', () async {
|
||||
final holder = _TestScopeHolder();
|
||||
final stream1 = holder.stream;
|
||||
final stream2 = holder.stream;
|
||||
expect(stream1, isNot(stream2));
|
||||
});
|
||||
|
||||
test('ask for a stream creates new listener and cancelling removes it',
|
||||
() async {
|
||||
final holder = _TestScopeHolder();
|
||||
final testHolder = TestableScopeStateHolder(holder);
|
||||
final stream1 = holder.stream;
|
||||
expect(testHolder.listeners.isEmpty, isTrue);
|
||||
final sub1 = stream1.listen((_) {});
|
||||
expect(testHolder.listeners.length, 1);
|
||||
final stream2 = holder.stream;
|
||||
expect(testHolder.listeners.length, 1);
|
||||
final sub2 = stream2.listen((_) {});
|
||||
expect(testHolder.listeners.length, 2);
|
||||
await sub1.cancel();
|
||||
expect(testHolder.listeners.length, 1);
|
||||
await sub2.cancel();
|
||||
expect(testHolder.listeners.isEmpty, isTrue);
|
||||
});
|
||||
});
|
||||
|
||||
group('stream of state', () {
|
||||
test('stream for scope happy path', () async {
|
||||
final holder = _TestScopeHolder();
|
||||
|
||||
int counter = 0;
|
||||
holder.stateStream.listen((state) {
|
||||
switch (counter % 4) {
|
||||
case 0:
|
||||
expect(state, isA<ScopeStateInitializing<_TestScopeContainer>>());
|
||||
break;
|
||||
case 1:
|
||||
expect(state, isA<ScopeStateAvailable<_TestScopeContainer>>());
|
||||
expect((state as ScopeStateAvailable).scope,
|
||||
isA<_TestScopeContainer>());
|
||||
break;
|
||||
case 2:
|
||||
expect(state, isA<ScopeStateDisposing<_TestScopeContainer>>());
|
||||
break;
|
||||
case 3:
|
||||
expect(state, isA<ScopeStateNone<_TestScopeContainer>>());
|
||||
break;
|
||||
default:
|
||||
}
|
||||
counter++;
|
||||
});
|
||||
|
||||
await holder.create();
|
||||
await holder.drop();
|
||||
await holder.create();
|
||||
await holder.drop();
|
||||
await holder.create();
|
||||
await holder.drop();
|
||||
});
|
||||
|
||||
test('after pausing stream of scope it emits buffered events', () async {
|
||||
final holder = _TestScopeHolder();
|
||||
|
||||
int counter = 0;
|
||||
final subscription = holder.stateStream.listen((state) {
|
||||
print(counter);
|
||||
print(state);
|
||||
switch (counter % 4) {
|
||||
case 0:
|
||||
expect(state, isA<ScopeStateInitializing<_TestScopeContainer>>());
|
||||
break;
|
||||
case 1:
|
||||
expect(state, isA<ScopeStateAvailable<_TestScopeContainer>>());
|
||||
expect((state as ScopeStateAvailable).scope,
|
||||
isA<_TestScopeContainer>());
|
||||
break;
|
||||
case 2:
|
||||
expect(state, isA<ScopeStateDisposing<_TestScopeContainer>>());
|
||||
break;
|
||||
case 3:
|
||||
expect(state, isA<ScopeStateNone<_TestScopeContainer>>());
|
||||
break;
|
||||
default:
|
||||
}
|
||||
counter++;
|
||||
});
|
||||
|
||||
await holder.create();
|
||||
await holder.drop();
|
||||
|
||||
subscription.pause();
|
||||
|
||||
await holder.create();
|
||||
await holder.drop();
|
||||
await holder.create();
|
||||
|
||||
subscription.resume();
|
||||
|
||||
await holder.drop();
|
||||
await holder.create();
|
||||
await holder.drop();
|
||||
|
||||
expect(counter, 8);
|
||||
});
|
||||
|
||||
test('reusing the same instance of stream cause an exception', () async {
|
||||
final holder = _TestScopeHolder();
|
||||
final stream = holder.stateStream;
|
||||
|
||||
stream.listen((_) {});
|
||||
try {
|
||||
stream.listen((_) {});
|
||||
fail('Must throw an exception');
|
||||
} on StateError catch (e) {
|
||||
expect(e.message, 'Stream has already been listened to.');
|
||||
}
|
||||
});
|
||||
|
||||
test('two different get calls return different streams', () async {
|
||||
final holder = _TestScopeHolder();
|
||||
final stream1 = holder.stateStream;
|
||||
final stream2 = holder.stateStream;
|
||||
expect(stream1, isNot(stream2));
|
||||
});
|
||||
|
||||
test('ask for a stream creates new listener and cancelling removes it',
|
||||
() async {
|
||||
final holder = _TestScopeHolder();
|
||||
final testHolder = TestableScopeStateHolder(holder);
|
||||
final stream1 = holder.stateStream;
|
||||
expect(testHolder.listeners.isEmpty, isTrue);
|
||||
final sub1 = stream1.listen((_) {});
|
||||
expect(testHolder.listeners.length, 1);
|
||||
final stream2 = holder.stateStream;
|
||||
expect(testHolder.listeners.length, 1);
|
||||
final sub2 = stream2.listen((_) {});
|
||||
expect(testHolder.listeners.length, 2);
|
||||
await sub1.cancel();
|
||||
expect(testHolder.listeners.length, 1);
|
||||
await sub2.cancel();
|
||||
expect(testHolder.listeners.isEmpty, isTrue);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class _TestScopeContainer extends ScopeContainer {}
|
||||
|
||||
class _TestScopeHolder extends ScopeHolder<_TestScopeContainer> {
|
||||
@override
|
||||
_TestScopeContainer createContainer() => _TestScopeContainer();
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:test/test.dart';
|
||||
import 'package:yx_scope/yx_scope.dart';
|
||||
|
||||
void main() {
|
||||
test('scope state is valid according to updates', () async {
|
||||
final scopeHolder = _TestScopeHolder();
|
||||
|
||||
final createCompleter = Completer();
|
||||
final dropCompleter = Completer();
|
||||
|
||||
expect(scopeHolder.state, isA<ScopeStateNone<_TestScopeContainer>>());
|
||||
expect(scopeHolder.state.none, isTrue);
|
||||
scopeHolder.create().then((_) => createCompleter.complete());
|
||||
|
||||
expect(
|
||||
scopeHolder.state, isA<ScopeStateInitializing<_TestScopeContainer>>());
|
||||
expect(scopeHolder.state.initializing, isTrue);
|
||||
await createCompleter.future;
|
||||
|
||||
expect(scopeHolder.state, isA<ScopeStateAvailable<_TestScopeContainer>>());
|
||||
expect(scopeHolder.state.available, isTrue);
|
||||
|
||||
scopeHolder.drop().then((_) => dropCompleter.complete());
|
||||
|
||||
expect(scopeHolder.state, isA<ScopeStateDisposing<_TestScopeContainer>>());
|
||||
expect(scopeHolder.state.disposing, isTrue);
|
||||
|
||||
await dropCompleter.future;
|
||||
|
||||
expect(scopeHolder.state, isA<ScopeStateNone<_TestScopeContainer>>());
|
||||
expect(scopeHolder.state.none, isTrue);
|
||||
});
|
||||
}
|
||||
|
||||
class _TestScopeHolder extends ScopeHolder<_TestScopeContainer> {
|
||||
@override
|
||||
_TestScopeContainer createContainer() => _TestScopeContainer();
|
||||
}
|
||||
|
||||
class _TestScopeContainer extends ScopeContainer {
|
||||
@override
|
||||
List<Set<AsyncDep>> get initializeQueue => [
|
||||
{_asyncDep}
|
||||
];
|
||||
|
||||
late final _asyncDep = rawAsyncDep(
|
||||
() => Future.delayed(Duration.zero),
|
||||
init: (dep) async => await dep,
|
||||
dispose: (dep) async {},
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,354 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:test/test.dart';
|
||||
import 'package:yx_scope/yx_scope.dart';
|
||||
|
||||
import 'utils/test_logger.dart';
|
||||
import 'utils/utils.dart';
|
||||
|
||||
void main() {
|
||||
setUp(() {
|
||||
ScopeObservatory.logger = const TestLogger();
|
||||
});
|
||||
|
||||
test('create and dispose scope works', () async {
|
||||
final holder = _TestScopeHolder();
|
||||
expect(holder.scope, isNull);
|
||||
|
||||
await holder.create();
|
||||
expect(holder.scope, isNotNull);
|
||||
|
||||
final scope = holder.scope;
|
||||
expect(ScopeContainerTestUtils.getDepCount(scope!), 1);
|
||||
await holder.drop();
|
||||
expect(ScopeContainerTestUtils.getDepCount(scope), 0);
|
||||
expect(holder.scope, isNull);
|
||||
});
|
||||
|
||||
test('second initialization call fails with an exception', () async {
|
||||
final holder = _TestScopeHolder();
|
||||
expect(holder.scope, isNull);
|
||||
|
||||
await holder.create();
|
||||
await expectThrown<ScopeException>(() async => holder.create());
|
||||
});
|
||||
|
||||
test('unawaited second initialization call fails with an exception',
|
||||
() async {
|
||||
final holder = _TestScopeHolder();
|
||||
expect(holder.scope, isNull);
|
||||
|
||||
// ignore: unawaited_futures
|
||||
holder.create();
|
||||
await expectThrown<ScopeException>(() async => holder.create());
|
||||
});
|
||||
|
||||
test(
|
||||
'sync drop call during initialization waits until fully initialized and then drops',
|
||||
() async {
|
||||
final depOverride = _TestAsyncDep();
|
||||
final holder = _TestScopeHolder(asyncDepOverride: depOverride);
|
||||
expect(holder.scope, isNull);
|
||||
|
||||
// ignore: unawaited_futures
|
||||
holder.create();
|
||||
await holder.drop();
|
||||
expect(depOverride._initialized, isFalse);
|
||||
expect(holder.scope, isNull);
|
||||
});
|
||||
|
||||
test(
|
||||
'sync create call during dispose waits until fully disposed and then init',
|
||||
() async {
|
||||
final depOverride = _TestAsyncDep();
|
||||
final holder = _TestScopeHolder(asyncDepOverride: depOverride);
|
||||
expect(holder.scope, isNull);
|
||||
|
||||
await holder.create();
|
||||
// ignore: unawaited_futures
|
||||
holder.drop();
|
||||
await holder.create();
|
||||
expect(depOverride._initialized, isTrue);
|
||||
expect(holder.scope, isNotNull);
|
||||
});
|
||||
|
||||
test(
|
||||
'second unawaited call for create after unawaited drop throws an exception',
|
||||
() async {
|
||||
final depOverride = _TestAsyncDep();
|
||||
final holder = _TestScopeHolder(asyncDepOverride: depOverride);
|
||||
expect(holder.scope, isNull);
|
||||
|
||||
await holder.create();
|
||||
// ignore: unawaited_futures
|
||||
holder.drop();
|
||||
// ignore: unawaited_futures
|
||||
holder.create();
|
||||
// ignore: unawaited_futures
|
||||
expectThrown<ScopeException>(() async => await holder.create());
|
||||
});
|
||||
|
||||
test(
|
||||
'second unawaited call for drop after unawaited create throws an exception',
|
||||
() async {
|
||||
final depOverride = _TestAsyncDep();
|
||||
final holder = _TestScopeHolder(asyncDepOverride: depOverride);
|
||||
expect(holder.scope, isNull);
|
||||
|
||||
await holder.create();
|
||||
// ignore: unawaited_futures
|
||||
await holder.drop();
|
||||
// ignore: unawaited_futures
|
||||
holder.create();
|
||||
// ignore: unawaited_futures
|
||||
holder.drop();
|
||||
expectThrown<ScopeException>(() async => await holder.drop());
|
||||
});
|
||||
|
||||
test('sync create call after drop throw an assertion', () async {
|
||||
final depOverride = _TestAsyncDep();
|
||||
final holder = _TestScopeHolder(asyncDepOverride: depOverride);
|
||||
expect(holder.scope, isNull);
|
||||
|
||||
await holder.create();
|
||||
// ignore: unawaited_futures
|
||||
holder.drop();
|
||||
await holder.create();
|
||||
expect(holder.scope, isNotNull);
|
||||
expect(holder.scope?.myAsyncDep.get._initialized, isTrue);
|
||||
});
|
||||
|
||||
test('sync second drop call fails with an assertion', () async {
|
||||
final depOverride = _TestAsyncDep();
|
||||
final holder = _TestScopeHolder(asyncDepOverride: depOverride);
|
||||
expect(holder.scope, isNull);
|
||||
|
||||
await holder.create();
|
||||
// ignore: unawaited_futures
|
||||
holder.drop();
|
||||
await expectAssertion(() async => holder.drop());
|
||||
});
|
||||
|
||||
test('handling exception during init and dispose of async deps', () async {
|
||||
final testAsyncDep = _TestAsyncDep();
|
||||
final brokenInitAsyncDep = _BrokenInitAsyncDep();
|
||||
final brokenDisposeAsyncDep = _BrokenDisposeAsyncDep();
|
||||
final holder = _BrokenAsyncDepScopeHolder(
|
||||
asyncDepOverride: testAsyncDep,
|
||||
brokenInitAsyncDepOverride: brokenInitAsyncDep,
|
||||
brokenDisposeAsyncDepOverride: brokenDisposeAsyncDep,
|
||||
);
|
||||
|
||||
try {
|
||||
await holder.create();
|
||||
fail('Must be an exception');
|
||||
} on BrokenInitException catch (_) {
|
||||
expect(testAsyncDep._initialized, isFalse);
|
||||
expect(brokenInitAsyncDep._disposed, isFalse);
|
||||
expect(brokenDisposeAsyncDep._initialized, isTrue);
|
||||
}
|
||||
|
||||
expect(holder.scope, isNull);
|
||||
});
|
||||
|
||||
test(
|
||||
'ScopeHolder creates different instances '
|
||||
'with different deps each new time', () async {
|
||||
final holder = _TestScopeHolder();
|
||||
expect(holder.scope, isNull);
|
||||
|
||||
_TestScopeContainer? lastScope;
|
||||
var scopeCounter = 0;
|
||||
var noScopeCounter = 0;
|
||||
final completer = Completer<void>();
|
||||
final removeListener = holder.listen((scope) {
|
||||
if (scope != null) {
|
||||
expect(lastScope, isNot(scope));
|
||||
lastScope = scope;
|
||||
scopeCounter++;
|
||||
} else {
|
||||
noScopeCounter++;
|
||||
}
|
||||
if (scopeCounter + noScopeCounter == 6) {
|
||||
completer.complete();
|
||||
}
|
||||
});
|
||||
|
||||
await holder.create();
|
||||
|
||||
final scope1 = holder.scope;
|
||||
final dep1 = holder.scope?.myAsyncDep.get;
|
||||
expect(scope1, isNotNull);
|
||||
expect(dep1, isNotNull);
|
||||
|
||||
await holder.drop();
|
||||
|
||||
expect(holder.scope, isNull);
|
||||
|
||||
await holder.create();
|
||||
|
||||
final scope2 = holder.scope;
|
||||
final dep2 = holder.scope?.myAsyncDep.get;
|
||||
expect(scope2, isNotNull);
|
||||
expect(dep2, isNotNull);
|
||||
|
||||
expect(scope1, isNot(scope2));
|
||||
expect(dep1, isNot(dep2));
|
||||
|
||||
await holder.drop();
|
||||
|
||||
await holder.create();
|
||||
|
||||
await holder.drop();
|
||||
|
||||
await completer.future;
|
||||
|
||||
expect(scopeCounter, 3);
|
||||
expect(noScopeCounter, 3);
|
||||
|
||||
removeListener();
|
||||
});
|
||||
|
||||
test('assertion when dep is called after scope is disposed', () async {
|
||||
final holder = _TestScopeHolder();
|
||||
expect(holder.scope, isNull);
|
||||
|
||||
await holder.create();
|
||||
final scope = holder.scope!;
|
||||
|
||||
await holder.drop();
|
||||
await expectAssertion(() => scope.myAsyncDep.get);
|
||||
});
|
||||
|
||||
test('exception when cached dep is called after scope is disposed', () async {
|
||||
final holder = _TestScopeHolder();
|
||||
expect(holder.scope, isNull);
|
||||
|
||||
await holder.create();
|
||||
final scope = holder.scope!;
|
||||
final dep = scope.syncDep;
|
||||
|
||||
await holder.drop();
|
||||
|
||||
await expectThrown<ScopeException>(() => dep.get);
|
||||
});
|
||||
}
|
||||
|
||||
class _TestScopeContainer extends ScopeContainer {
|
||||
final _TestAsyncDep? asyncDepOverride;
|
||||
|
||||
_TestScopeContainer({this.asyncDepOverride});
|
||||
|
||||
@override
|
||||
List<Set<AsyncDep>> get initializeQueue => [
|
||||
{myAsyncDep}
|
||||
];
|
||||
|
||||
late final myAsyncDep = asyncDep(() => asyncDepOverride ?? _TestAsyncDep());
|
||||
|
||||
late final syncDep = dep(() => _SyncDep());
|
||||
}
|
||||
|
||||
class _TestScopeHolder extends ScopeHolder<_TestScopeContainer> {
|
||||
final _TestAsyncDep? asyncDepOverride;
|
||||
|
||||
_TestScopeHolder({this.asyncDepOverride});
|
||||
|
||||
@override
|
||||
_TestScopeContainer createContainer() =>
|
||||
_TestScopeContainer(asyncDepOverride: asyncDepOverride);
|
||||
}
|
||||
|
||||
class _SyncDep {}
|
||||
|
||||
class _TestAsyncDep implements AsyncLifecycle {
|
||||
var _initialized = false;
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
_initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
class _BrokenAsyncDepScopeContainer extends ScopeContainer {
|
||||
final _TestAsyncDep? asyncDepOverride;
|
||||
final _BrokenInitAsyncDep? brokenInitAsyncDepOverride;
|
||||
final _BrokenDisposeAsyncDep? brokenDisposeAsyncDepOverride;
|
||||
|
||||
_BrokenAsyncDepScopeContainer({
|
||||
this.asyncDepOverride,
|
||||
this.brokenInitAsyncDepOverride,
|
||||
this.brokenDisposeAsyncDepOverride,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Set<AsyncDep>> get initializeQueue => [
|
||||
{testDep},
|
||||
{brokenDisposeDep},
|
||||
{brokenInitDep},
|
||||
];
|
||||
|
||||
late final testDep = asyncDep(() => asyncDepOverride ?? _TestAsyncDep());
|
||||
|
||||
late final brokenInitDep =
|
||||
asyncDep(() => brokenInitAsyncDepOverride ?? _BrokenInitAsyncDep());
|
||||
|
||||
late final brokenDisposeDep =
|
||||
asyncDep(() => brokenDisposeAsyncDepOverride ?? _BrokenDisposeAsyncDep());
|
||||
}
|
||||
|
||||
class _BrokenAsyncDepScopeHolder
|
||||
extends ScopeHolder<_BrokenAsyncDepScopeContainer> {
|
||||
final _TestAsyncDep? asyncDepOverride;
|
||||
final _BrokenInitAsyncDep? brokenInitAsyncDepOverride;
|
||||
final _BrokenDisposeAsyncDep? brokenDisposeAsyncDepOverride;
|
||||
|
||||
_BrokenAsyncDepScopeHolder({
|
||||
this.asyncDepOverride,
|
||||
this.brokenInitAsyncDepOverride,
|
||||
this.brokenDisposeAsyncDepOverride,
|
||||
});
|
||||
|
||||
@override
|
||||
_BrokenAsyncDepScopeContainer createContainer() =>
|
||||
_BrokenAsyncDepScopeContainer(
|
||||
asyncDepOverride: asyncDepOverride,
|
||||
brokenInitAsyncDepOverride: brokenInitAsyncDepOverride,
|
||||
brokenDisposeAsyncDepOverride: brokenDisposeAsyncDepOverride,
|
||||
);
|
||||
}
|
||||
|
||||
class _BrokenInitAsyncDep implements AsyncLifecycle {
|
||||
var _disposed = false;
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
throw BrokenInitException();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
class _BrokenDisposeAsyncDep implements AsyncLifecycle {
|
||||
var _initialized = false;
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
throw Exception('broken dispose');
|
||||
}
|
||||
}
|
||||
|
||||
class BrokenInitException implements Exception {}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
import 'package:yx_scope/src/monitoring/scope_observatory.dart';
|
||||
|
||||
class TestLogger extends ScopeLogger {
|
||||
const TestLogger();
|
||||
|
||||
@override
|
||||
void log(
|
||||
LogType type,
|
||||
Object message, {
|
||||
Object? exception,
|
||||
StackTrace? stackTrace,
|
||||
}) {
|
||||
if (type != LogType.error) {
|
||||
// ignore: avoid_print
|
||||
print('${type.name}: $message');
|
||||
} else {
|
||||
// ignore: avoid_print
|
||||
print('ERROR: $message\n$exception\n$stackTrace');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:test/test.dart';
|
||||
|
||||
Future<void> expectThrown<T extends Object>(
|
||||
FutureOr<void> Function() callback,
|
||||
) async {
|
||||
try {
|
||||
await callback();
|
||||
fail('There must be $T thrown');
|
||||
// ignore: avoid_catching_errors
|
||||
} on T catch (_) {}
|
||||
}
|
||||
|
||||
Future<void> expectAssertion(
|
||||
FutureOr<void> Function() callback,
|
||||
) async =>
|
||||
expectThrown<AssertionError>(callback);
|
||||
32
mnemo_cards_web_v2/packages/yx/city-services-pub/yx_scope/packages/yx_scope_flutter/.gitignore
vendored
Normal file
32
mnemo_cards_web_v2/packages/yx/city-services-pub/yx_scope/packages/yx_scope_flutter/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
migrate_working_dir/
|
||||
|
||||
# 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/
|
||||
|
||||
.arcignore
|
||||
|
|
@ -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: e2874b6e235bcae425ed3436c113ab4ac50ac707
|
||||
channel: unknown
|
||||
|
||||
project_type: package
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
## 1.1.3 - unpublished
|
||||
|
||||
* Fix: handling of ScopeHolder state changes in ScopeListener subscription
|
||||
|
||||
## 1.1.2 - 2025.07.30
|
||||
|
||||
* Fix: ScopeBuilder didUpdateWidget throw error if there is no
|
||||
parent ScopeProvider but scopeHolder passed as argument
|
||||
* ScopeBuilder test added
|
||||
|
||||
## 1.1.1 - 2025.07.08
|
||||
|
||||
* Documentation links fixed
|
||||
|
||||
## 1.1.0 - 2025.07.07
|
||||
|
||||
* Fix: ScopeBuilder and ScopeListener fixes, tests added
|
||||
* Fix: mounted check added to _subscribe method
|
||||
* Add test for checking rebuilds only for dependent widgets
|
||||
* New Flutter version added to CI
|
||||
* Documentation fixes and updates
|
||||
|
||||
## 1.0.2 - 2024.12.18
|
||||
|
||||
* Updated repository link
|
||||
* Library topics added
|
||||
|
||||
## 1.0.1 - 2024.10.23
|
||||
|
||||
* Minor fixes in links
|
||||
|
||||
## 1.0.0 — 2024.10.18
|
||||
|
||||
* Ready to be open-source
|
||||
|
||||
## 0.0.2 — 2024.09.23
|
||||
|
||||
* [ScopeBuilder] API changed: scopeStateHolder -> holder
|
||||
|
||||
## 0.0.1
|
||||
|
||||
* Add [ScopeProvider], [ScopeConsumer], [ScopeBuilder], [ScopeListener] widgets
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2024 YANDEX LLC
|
||||
|
||||
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.
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
yx_scope_flutter package is an adapter for yx_scope package for using it with Flutter.
|
||||
|
||||
## Features
|
||||
|
||||
`ScopeProvider` is an `InheritedWidget` that passes your scope down to any widget in the subtree.
|
||||
|
||||
`ScopeBuilder` handles building a widget in response to `scope`.
|
||||
|
||||
`ScopeListener` is a widget that should be used for functionality that needs to occur only in
|
||||
response to a `scope` change such as navigation, showing a `SnackBar`, showing a `Dialog`, etc...
|
||||
|
||||
`ScopeConsumer` is analogous to a nested `ScopeListener` and `ScopeBuilder` but reduces the amount
|
||||
of boilerplate needed.
|
||||
|
||||
## Usage
|
||||
|
||||
Lets take a look at how to use `ScopeProvider` to provide `RootScopeHolder` and react to scope
|
||||
changes with `ScopeBuilder`
|
||||
|
||||
1. Create instance of `RootScopeHolder` and run app
|
||||
|
||||
```dart
|
||||
void main() {
|
||||
final rootScopeHolder = RootScopeHolder();
|
||||
rootScopeHolder.create();
|
||||
runApp(App(scopeHolder: rootScopeHolder));
|
||||
}
|
||||
```
|
||||
|
||||
2. Use `ScopeProvider` to provide `RootScopeContainer`
|
||||
|
||||
```dart
|
||||
class App extends StatelessWidget {
|
||||
final RootScopeHolder scopeHolder;
|
||||
|
||||
const App({required this.scopeHolder, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ScopeProvider<RootScopeContainer>(
|
||||
holder: scopeHolder,
|
||||
child: MaterialApp(
|
||||
title: 'YxScopedFlutter Demo',
|
||||
home: const HomePage(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. Use `ScopeBuilder` to react to scope changes for build widget
|
||||
|
||||
```dart
|
||||
// HomePage widget
|
||||
//...
|
||||
Widget build() =>
|
||||
ScopeBuilder<RootScopeContainer>.withPlaceholder(
|
||||
builder: (context, rootScope) => SomeWidget(rootScope),
|
||||
placeholder: const SizedBox.shrink(),
|
||||
);
|
||||
// ...
|
||||
```
|
||||
|
||||
## More details
|
||||
|
||||
Read full introduction to the library in [the documentation](https://github.com/yandex/city-services-pub/blob/main/yx_scope/packages/yx_scope_flutter/doc/introduction.md).
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
|
|
@ -0,0 +1,543 @@
|
|||
## Quick Start
|
||||
|
||||
Let's look at a simple dependency container. First, add yx_scope to your pubspec.yaml:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
yx_scope: ^1.0.0
|
||||
```
|
||||
|
||||
Create a file named `app_scope.dart` and add the description of our container and dependencies:
|
||||
|
||||
```dart
|
||||
class AppScopeContainer extends ScopeContainer {
|
||||
late final routerDelegateDep = dep(() => AppRouterDelegate());
|
||||
|
||||
late final appStateObserverDep = dep(
|
||||
() =>
|
||||
AppStateObserver(
|
||||
routerDelegateDep.get,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class AppScopeHolder extends ScopeHolder<AppScopeContainer> {
|
||||
@override
|
||||
AppScopeContainer createContainer() => AppScopeContainer();
|
||||
}
|
||||
```
|
||||
|
||||
Now, let's create an `AppScopeHolder`, create a container, and access the dependencies:
|
||||
|
||||
```dart
|
||||
void main() async {
|
||||
final appScopeHolder = AppScopeHolder();
|
||||
await appScopeHolder.create();
|
||||
|
||||
final appScope = appScopeHolder.scope;
|
||||
if (appScope != null) {
|
||||
final AppStateObserver appStateObserver = appScope.appStateObserverDep.get;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
An important feature of the library is that we work with the DI container without binding it to the
|
||||
UI. The DI container is primary, and only as an addition to this, the container can be attached to
|
||||
the UI.
|
||||
|
||||
The DI container is created as a reaction to an event in the application's logic, not as a result of
|
||||
the appearance of some screen or UI element. UI does not generate scopes; scopes generate UI.
|
||||
|
||||
This is an important principle of the mechanics of yx_scope.
|
||||
|
||||
## Key Entities
|
||||
|
||||
- **Dep (dependency)**: A container for one specific instance of any entity.
|
||||
- **ScopeContainer**: An isolated, non-overlapping set of dependencies united by a meaningful scope
|
||||
and sharing a common lifecycle.
|
||||
- **ScopeHolder**: An instance that stores the current state of the container and is responsible for
|
||||
its initialization and disposal.
|
||||
|
||||
> A ScopeContainer can be closed by some public interface, hiding implementation details and access
|
||||
> to Dep. In this case, the interface for it will have the suffix Scope, for example, AccountScope.
|
||||
|
||||
`ScopeHolder` is responsible for creating and removing the scope using the `create` and `drop`
|
||||
methods.
|
||||
|
||||
Initially, `ScopeHolder` contains a null state.
|
||||
|
||||
After calling the `create` method, a scope appears - a container with dependencies that can be
|
||||
accessed through the `ScopeHolder`.
|
||||
|
||||
After the `drop` method, all scope dependencies are disposed - the `ScopeHolder` again contains
|
||||
null.
|
||||
|
||||
Due to null-safety, `ScopeHolder` provides a compile-safe check for the existence of a scope
|
||||
directly
|
||||
at the time of writing code, not at runtime.
|
||||
|
||||

|
||||
|
||||
## Scope
|
||||
|
||||
A scope is a container with a set of dependencies, whose lifetime is limited by its life cycle. The
|
||||
creation and removal of a scope occur based on specific conditions during the application's runtime,
|
||||
and these **conditions and the life cycle differ from other scopes**. This is extremely
|
||||
important. The fact that the logic of yx_scope revolves around the concept of a "scope" does not
|
||||
mean that scopes should be created for everything. A well-structured organization of scope
|
||||
hierarchies is key to making yx_scope convenient to use.
|
||||
|
||||
You should only create a separate scope when the life cycle of a group of dependencies has clear
|
||||
conditions for both the start and end of their existence, and these conditions differ from the life
|
||||
cycles of other scopes. In other words, the start of one scope may not coincide with any other, and
|
||||
the end may also differ from other scopes.
|
||||
|
||||
> **When a Scope is Not Needed**
|
||||
>
|
||||
> If you're unsure what specific condition should trigger the creation of a container with a set of
|
||||
> dependencies (and what specific condition should cause its termination), then creating a separate
|
||||
> scope is unnecessary.
|
||||
>
|
||||
> If both the start and end of the life cycle of a group of dependencies coincide with those of
|
||||
> another scope, there is no need to create a separate scope. Such a group of dependencies can be
|
||||
> placed within an existing scope, and combined using a ScopeModule (which will be discussed later).
|
||||
|
||||
## Scope Tree
|
||||
|
||||
Scopes can relate to each other in two ways:
|
||||
|
||||
1. **Siblings**: Scopes at the same nesting level and in the same visibility area.
|
||||
2. **Parent-child**: One scope acts as a parent, the other as a child.
|
||||
|
||||

|
||||
|
||||
In the diagram, sibling scopes are scopes of the same color.
|
||||
|
||||
- The purple scope is the parent for the blue ones: `Account` and `Register`.
|
||||
- The blue `AccountScope` is the parent for the green scopes: `Online`, `Order`, `Map`.
|
||||
- The green `MapScope` is the parent for the yellow `MapNavigationScope`.
|
||||
- However, the yellow `MapNavigationScope` is not a sibling of a child scope from, for
|
||||
example, `OrderScope`, even though they are on the same level.
|
||||
|
||||
How to determine the relationship between two scopes? Use the following checks:
|
||||
|
||||
- If two groups of dependencies have different life cycles (LC) that may overlap in time in any
|
||||
order (or not overlap at all), then you need two sibling scopes.
|
||||
- If two groups of dependencies have different LCs, but one can only exist strictly within the LC of
|
||||
the other, then the first scope is a child, and the second is the parent.
|
||||
|
||||
- `AppScope` exists for the entire life of the application.
|
||||
- `AccountScope` exists within the `AppScope` and appears only when the user logs in, disappearing
|
||||
when they log out.
|
||||
- `RegisterScope` exists within the `AppScope` and appears when the user goes to the registration
|
||||
page. The life cycles of `AccountScope` and `RegisterScope` do not overlap. In the future, a user
|
||||
might be able to register while already logged in, which would cause the life cycles to overlap,
|
||||
but they would still not depend on each other. Thus, they are sibling scopes.
|
||||
- `OnlineScope`, `OrderScope`, and `MapScope` can only exist if the user is logged in and within
|
||||
the `AccountScope`. The life cycle of each of these three scopes is independent of the
|
||||
others: `OnlineScope` appears when the user is ready to take orders, `OrderScope` appears when the
|
||||
user has received an order, and `MapScope` appears when the user navigates to the map screen. All
|
||||
these scopes must close if the user logs out (i.e., when `AccountScope` closes).
|
||||
- `MapNavigationScope` — navigation on the map can only occur when the map is open and `MapScope`
|
||||
exists. However, the map can be open without navigation, meaning the start of the life cycle
|
||||
of `MapNavigationScope` differs from that of `MapScope`. Therefore, this scope is separated.
|
||||
|
||||
Here's an example of how the scope tree looks in the code, based on `AppScope`:
|
||||
|
||||
```dart
|
||||
class AppScopeContainer extends ScopeContainer {
|
||||
|
||||
// The child scope of AppScope is simply a nested ScopeHolder of another scope
|
||||
late final accountScopeHolderDep = dep(() => AccountScopeHolder(this));
|
||||
|
||||
late final registerScopeHolderDep = dep(() => RegisterScopeHolder(this));
|
||||
|
||||
late final routerDelegateDep = dep(() => AppRouterDelegate());
|
||||
|
||||
late final appStateObserverDep = dep(
|
||||
() =>
|
||||
AppStateObserver(
|
||||
routerDelegateDep.get,
|
||||
accountScopeHolderDep.get,
|
||||
registerScopeHolderDep.get,
|
||||
),
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Types of Scopes
|
||||
|
||||
The type of scope depends on two questions:
|
||||
|
||||
1. Does the scope have data without which it cannot exist?
|
||||
2. Is there a scope without which the new scope cannot exist?
|
||||
|
||||
The combination of answers to these questions will lead us to one of four types:
|
||||
|
||||
- 1 No, 2 No (grey) -> ScopeContainer + ScopeHolder
|
||||
- 1 Yes, 2 No (blue) -> DataScopeContainer + DataScopeHolder
|
||||
- 1 No, 2 Yes (green) -> ChildScopeContainer + ChildScopeHolder
|
||||
- 1 Yes, 2 Yes (green+blue) -> ChildDataScopeContainer + ChildDataScopeHolder
|
||||
|
||||

|
||||
|
||||
## ScopeModule
|
||||
|
||||
If the lifecycle of two groups of dependencies completely coincides, but these dependencies differ
|
||||
in meaning and you want to separate them from each other, then the correct solution would be to use
|
||||
`ScopeModule`.
|
||||
|
||||
```dart
|
||||
class AppScopeContainer extends ScopeContainer {
|
||||
late final accountScopeHolderDep = dep(() => AccountScopeHolder(this));
|
||||
|
||||
late final registerScopeHolderDep = dep(() => RegisterScopeHolder(this));
|
||||
|
||||
// Routing entities in one module
|
||||
late final routingModule = RoutingAppScopeModule(this);
|
||||
}
|
||||
|
||||
class RoutingAppScopeModule extends ScopeModule<AppScopeContainer> {
|
||||
RoutingAppScopeModule(super.container);
|
||||
|
||||
late final routerDelegateDep = dep(() => AppRouterDelegate());
|
||||
|
||||
late final appStateObserverDep = dep(
|
||||
() =>
|
||||
AppStateObserver(
|
||||
routerDelegateDep.get,
|
||||
// We can access deps from the container
|
||||
container.accountScopeHolderDep.get,
|
||||
container.registerScopeHolderDep.get,
|
||||
),
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Asynchronous Dependencies
|
||||
|
||||
There are dependencies that, in addition to instantiation, require additional initialization, often
|
||||
asynchronous. Such dependencies are declared in a special way in the container, but most
|
||||
importantly, they must be specified in the initialization queue for this scope.
|
||||
|
||||
```dart
|
||||
class AppScopeContainer extends ScopeContainer {
|
||||
|
||||
@override
|
||||
List<Set<AsyncDep>> get initializeQueue =>
|
||||
[
|
||||
{
|
||||
appStateObserverDep,
|
||||
}
|
||||
];
|
||||
|
||||
late final routerDelegateDep = dep(() => AppRouterDelegate());
|
||||
|
||||
late final appStateObserverDep = rawAsyncDep(
|
||||
() =>
|
||||
AppStateObserver(
|
||||
routerDelegateDep.get,
|
||||
),
|
||||
init: (dep) async => dep.init(),
|
||||
dispose: (dep) async => dep.dispose(),
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
initializeQueue is an initialization queue consisting of a list of sets of dependencies. All
|
||||
dependencies within the same set are initialized in parallel (simplified, this can be done via
|
||||
`Future.wait([])` ([doc](https://api.dart.dev/stable/2.17.3/dart-async/Future/wait.html))).
|
||||
Dependencies in different sets are initialized sequentially — first, all dependencies in the set
|
||||
with the lower index in the list are initialized, followed by those in the set with the higher
|
||||
index.
|
||||
|
||||
The initialization of the entire scope completes once all asynchronous dependencies have been
|
||||
initialized. That is why the `create()` method is asynchronous.
|
||||
|
||||
There are two ways to make a dependency asynchronous — by using `rawAsyncDep` or by implementing the
|
||||
`AsyncLifecycle` interface, which contains the init and dispose methods. To declare such a
|
||||
dependency, you can use the `asyncDep` method.
|
||||
|
||||
The init and dispose methods, as well as their callbacks, are required to be asynchronous. This is
|
||||
intentional, even for cases where a dependency can be initialized synchronously. Enforcing
|
||||
asynchronous callbacks ensures that asynchronous calls inside them will not execute without await (
|
||||
enforced by the `unawaited_future` lint rule).
|
||||
|
||||
However, if you are certain that a dependency can be initialized after the scope has already
|
||||
appeared and is ready to work, you can choose not to await its initialization (ignore the linter
|
||||
using `//ignore: unawaited_future`).
|
||||
|
||||
## Scope Interface
|
||||
|
||||
So far, we've been directly working with the `ScopeContainer` implementation. This is fast and
|
||||
convenient, but it increases code coupling. In fact, any scope, no matter how deeply nested, can
|
||||
access any other part of the scope tree through the chain. This creates highly complex and
|
||||
potentially dangerous interdependencies between scopes and their dependencies.
|
||||
|
||||
A more flexible and scalable approach to working with scope containers is to use interfaces derived
|
||||
from the base `Scope` interface. This was mentioned earlier in the "Key Entities" section.
|
||||
|
||||
Let's look at an example with `OnlineScope`. Recall that this scope appears when the user selects
|
||||
that they are ready to receive orders in the application:
|
||||
|
||||
```dart
|
||||
abstract class OnlineScope implements Scope {
|
||||
AcceptOrderManager get acceptOrderManager; // manager to accept incoming orders
|
||||
}
|
||||
|
||||
class OnlineScopeContainer extends ChildScopeContainer<AccountScopeContainer>
|
||||
implements OnlineScope {
|
||||
|
||||
OnlineScopeContainer({required super.parent});
|
||||
|
||||
late final _acceptOrderManagerDep = dep(() =>
|
||||
AcceptOrderManager(
|
||||
parent.ordersStateHolderDep,
|
||||
),
|
||||
);
|
||||
|
||||
@override
|
||||
AcceptOrderManager get acceptOrderManager => _acceptOrderManagerDep.get;
|
||||
}
|
||||
```
|
||||
|
||||
Note: `OnlineScopeContainer` now implements the `OnlineScope` interface, which requires overriding
|
||||
the getter for `AcceptOrderManager`. We implement it in the container by returning the value from
|
||||
the `_acceptOrderManagerDep` container. This container can now be made private, meaning no one
|
||||
outside the scope file will access it directly.
|
||||
|
||||
Let’s also modify the `OnlineScopeHolder`:
|
||||
|
||||
```dart
|
||||
// Now inherits from BaseChildScopeHolder
|
||||
class OnlineScopeHolder extends BaseChildScopeHolder<OnlineScope,
|
||||
OnlineScopeContainer,
|
||||
AccountScopeContainer> {
|
||||
OnlineScopeHolder(super.parent);
|
||||
|
||||
@override
|
||||
OnlineScopeContainer createContainer(AccountScopeContainer parent) =>
|
||||
OnlineScopeContainer(parent: parent);
|
||||
}
|
||||
```
|
||||
|
||||
Now `OnlineScopeHolder` inherits from `BaseChildScopeHolder`. This allows us to specify the
|
||||
interface as the first generic parameter. In turn, this completely hides the container from
|
||||
consumers: now, when accessing `onlineScopeHolder.scope`, we will work with the `OnlineScope`
|
||||
interface instead of the container itself. This completely hides the container's origin from users.
|
||||
|
||||
We can apply the same approach with `ScopeHolder`:
|
||||
|
||||
```dart
|
||||
// This interface is located in the domain layer of the application
|
||||
abstract class OnlineOrderStateHolder {
|
||||
Future<void> toggle();
|
||||
}
|
||||
|
||||
class OnlineScopeHolder extends BaseChildScopeHolder<OnlineScope,
|
||||
OnlineScopeContainer,
|
||||
AccountScopeContainer>
|
||||
implements OnlineOrderStateHolder {
|
||||
OnlineScopeHolder(super.parent);
|
||||
|
||||
@override
|
||||
OnlineScopeContainer createContainer(OnlineScopeParent parent) =>
|
||||
OnlineScopeContainer(parent: parent);
|
||||
|
||||
@override
|
||||
Future<void> toggle() async {
|
||||
if (scope == null) {
|
||||
await create();
|
||||
} else {
|
||||
await drop();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In this way, in the domain layer of the application, we can work only with
|
||||
the `OnlineOrderStateHolder` interface, without worrying about its implementation or the fact that
|
||||
it's a `ScopeHolder`.
|
||||
|
||||
**Tip**: Whenever possible, always try to work with `ScopeHolder` and `ScopeContainer` through
|
||||
interfaces. This will significantly reduce the coupling between your domain layer and the DI control
|
||||
entities.
|
||||
|
||||
## Interaction Between Scopes via Interfaces
|
||||
|
||||
We've protected against using raw implementations of scopes from outside, but the scopes themselves
|
||||
are still interconnected: `OnlineScopeContainer` knows that its parent is `AccountScopeContainer`.
|
||||
This means it can access any dependency in that container, including holders of other scopes,
|
||||
potentially creating implicit links between scopes from completely different parts of the tree.
|
||||
|
||||
However, interaction between scopes is still necessary. For example, inside `OrderScope`, we might
|
||||
want to trigger navigation. Somehow, we need to link scopes across different levels and parents.
|
||||
This can be done through the parent's `Scope` interface.
|
||||
|
||||
Let’s continue the example with `OnlineScope`:
|
||||
|
||||
```dart
|
||||
abstract class OnlineScope implements Scope {
|
||||
AcceptOrderManager get acceptOrderManager;
|
||||
}
|
||||
|
||||
/// Interface describing what entities the parent must contain
|
||||
/// to satisfy the expectations of the child scope
|
||||
abstract class OnlineScopeParent implements Scope {
|
||||
OrderStateHolder get orderStateHolder;
|
||||
}
|
||||
|
||||
/// Now ChildScopeContainer is typed not with AccountScopeContainer,
|
||||
/// but with the interface expected from the parent
|
||||
class OnlineScopeContainer extends ChildScopeContainer<OnlineScopeParent>
|
||||
implements OnlineScope {
|
||||
|
||||
// Dependencies
|
||||
}
|
||||
|
||||
/// ScopeHolder is also typed with OnlineScopeParent
|
||||
class OnlineScopeHolder extends BaseChildScopeHolder<OnlineScope,
|
||||
OnlineScopeContainer,
|
||||
OnlineScopeParent>
|
||||
implements OnlineOrderStateHolder {
|
||||
// Implementation
|
||||
}
|
||||
```
|
||||
|
||||
Now `OnlineScope` becomes truly independent. Its consumers use the `OnlineScope` interface, and it
|
||||
only knows the necessary minimum about its parent to function.
|
||||
|
||||
To ensure that the parent scope meets the required contract, we must implement it:
|
||||
|
||||
```dart
|
||||
class AccountScopeContainer extends ChildDataScopeContainer<AccountScopeParent, Account>
|
||||
implements AccountScope, OnlineScopeParent {
|
||||
AccountScopeContainer({
|
||||
required super.parent,
|
||||
required super.data,
|
||||
});
|
||||
|
||||
late final _onlineScopeHolderDep = dep(() => OnlineScopeHolder(this));
|
||||
|
||||
late final _orderScopeHolderDep = dep(() => OrderScopeHolder(this));
|
||||
|
||||
late final _mapScopeHolderDep = dep(() => MapScopeHolder());
|
||||
|
||||
@override
|
||||
OrderStateHolder get orderStateHolder => _orderScopeHolderDep.get;
|
||||
}
|
||||
```
|
||||
|
||||
Here, `AccountScopeContainer` implements both its own `AccountScope` interface and
|
||||
the `OnlineScopeParent` interface.
|
||||
|
||||
**Tip**: Do not return parent scopes transitively down through interfaces. `OnlineScopeParent`
|
||||
should not contain a getter for `AccountScopeParent`. This would break isolation by forcing the
|
||||
scope to implicitly know about the parent's implementation. If you need entities from
|
||||
the `AccountScopeParent` parent scope, declare them explicitly in the `OnlineScopeParent` interface.
|
||||
|
||||
The benefits of this additional boilerplate is that If all scopes are strictly organized according
|
||||
to the rule of using interfaces, you can change the hierarchy of your scope tree almost painlessly.
|
||||
This is crucial in a growing product, as the requirements and life cycles of various app flows may
|
||||
inevitably change. When moving a child scope from one parent to another, you’ll only need to modify
|
||||
those parent scopes. The child scope itself will remain unchanged.
|
||||
|
||||
## In the Widget Tree
|
||||
|
||||
The core of `yx_scope` is pure Dart, but the scopes are designed to integrate with Flutter widgets.
|
||||
For this, you can use the `yx_scope_flutter` package, which provides a set of widget adapters for
|
||||
your `ScopeHolder` objects.
|
||||
|
||||
Here's a basic example of how to inject a root scope into the widget tree:
|
||||
|
||||
```dart
|
||||
class App extends StatefulWidget {
|
||||
const App({super.key});
|
||||
|
||||
@override
|
||||
State<App> createState() => _AppState();
|
||||
}
|
||||
|
||||
class _AppState extends State<App> {
|
||||
final _appScopeHolder = AppScopeHolder();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_appScopeHolder.create();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_appScopeHolder.drop();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ScopeProvider(
|
||||
holder: _appScopeHolder,
|
||||
// Scope widgets support working with Scope interfaces
|
||||
child: ScopeBuilder<AppScopeContainer>.withPlaceholder(
|
||||
builder: (context, appScope) {
|
||||
return MaterialApp.router(
|
||||
title: 'YxScopedFlutter Demo',
|
||||
routerDelegate: appScope.routerDelegateDep.get,
|
||||
);
|
||||
},
|
||||
// Displays this widget while [appScopeHolder] is loading
|
||||
placeholder: const Center(child: CircularProgressIndicator()),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Notice that `AppScopeHolder` is a field inside the `State` of your app. This is one of the most
|
||||
significant advantages of `yx_scope`: the entire scope tree (i.e., your whole DI) consists of
|
||||
non-static `ScopeHolder` instances that are localized to their place in the tree. The
|
||||
root `ScopeHolder` instance only exists where you instantiate it, and any child `ScopeHolder`
|
||||
instance only exists if its direct parent scope exists. In some sense, you could call this a
|
||||
*hyper-local* scoping system.
|
||||
|
||||
## Static Analysis
|
||||
|
||||
Another essential part of `yx_scope` is a set of static analysis rules to guide best practices when
|
||||
working with the framework. To use it, add the following to your `pubspec.yaml`
|
||||
under `dev_dependencies`:
|
||||
|
||||
```yaml
|
||||
dev_dependencies:
|
||||
yx_scope_linter: ^1.0.0
|
||||
custom_lint: ^0.5.3
|
||||
```
|
||||
|
||||
Then, run `pub get`. After that, add this to your `analysis_options.yaml`:
|
||||
|
||||
```yaml
|
||||
analyzer:
|
||||
plugins:
|
||||
- custom_lint
|
||||
```
|
||||
|
||||
With this setup, the linter will remind you to add the `Dep` suffix, declare fields as `final`,
|
||||
highlight uninitialized asynchronous dependencies, detect cyclic dependencies without any code
|
||||
generation, and even show the full list of entities involved in the cycle.
|
||||
|
||||
This isn't a comprehensive set of rules yet. You can find the current list in the `yx_scope_linter`
|
||||
README.md. Manual rules not yet automated can be found in `docs/manual_linter.md`, and we’ll
|
||||
gradually automate them over time.
|
||||
|
||||
---
|
||||
|
||||
We've now covered the fundamental principles of how the `yx_scope` library works, from global
|
||||
container basics to scope separation via interfaces and how to link scopes together.
|
||||
|
||||
You can find a more detailed [app example](../example) using scopes in the `yx_scope_flutter`
|
||||
repository. It also includes an example of creating dynamic scopes of the same type, such
|
||||
as `OrderScope` for handling multiple orders.
|
||||
|
||||
If you're interested in the issues and motivations behind creating `yx_scope`, check out the [Dev
|
||||
Day/Night](https://www.youtube.com/watch?v=3hcDcdfk0pQ&list=PLQC2_0cDcSKD7Bfrg0U36Q2KDiy9dLdmb) (ru)
|
||||
conference talk. Note that the talk covers an earlier version of the library, where it was
|
||||
called `yx_scoped` and `ScopeContainer` was called `ScopeNode`—don’t let that confuse you.
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
migrate_working_dir/
|
||||
|
||||
# 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
|
||||
**/doc/api/
|
||||
**/ios/Flutter/.last_build_id
|
||||
.dart_tool/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
.packages
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
/pubspec.lock
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
|
||||
# Android Studio will place build artifacts here
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
|
||||
.arcignore
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
# 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.
|
||||
|
||||
version:
|
||||
revision: e2874b6e235bcae425ed3436c113ab4ac50ac707
|
||||
channel: unknown
|
||||
|
||||
project_type: app
|
||||
|
||||
# Tracks metadata for the flutter migrate command
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: e2874b6e235bcae425ed3436c113ab4ac50ac707
|
||||
base_revision: e2874b6e235bcae425ed3436c113ab4ac50ac707
|
||||
- platform: android
|
||||
create_revision: e2874b6e235bcae425ed3436c113ab4ac50ac707
|
||||
base_revision: e2874b6e235bcae425ed3436c113ab4ac50ac707
|
||||
|
||||
# User provided section
|
||||
|
||||
# List of Local paths (relative to this file) that should be
|
||||
# ignored by the migrate tool.
|
||||
#
|
||||
# Files that are not part of the templates will be ignored by default.
|
||||
unmanaged_files:
|
||||
- 'lib/main.dart'
|
||||
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# yx_scope_flutter_example
|
||||
|
||||
A new Flutter project.
|
||||
|
||||
## Getting Started
|
||||
|
||||
This project is a starting point for a Flutter application.
|
||||
|
||||
A few resources to get you started if this is your first Flutter project:
|
||||
|
||||
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
||||
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
|
||||
|
||||
For help getting started with Flutter development, view the
|
||||
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
||||
samples, guidance on mobile development, and a full API reference.
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
linter:
|
||||
rules:
|
||||
- implementation_imports # https://dart-lang.github.io/linter/lints/implementation_imports.html
|
||||
- prefer_relative_imports # https://dart-lang.github.io/linter/lints/prefer_relative_imports.html
|
||||
- directives_ordering # https://dart-lang.github.io/linter/lints/directives_ordering.html
|
||||
- unawaited_futures # https://dart-lang.github.io/linter/lints/unawaited_futures.html
|
||||
|
||||
analyzer:
|
||||
plugins:
|
||||
- custom_lint
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
|
||||
class MapController extends ChangeNotifier {
|
||||
static const totalPositions = 100;
|
||||
static const itemHeight = 56.0;
|
||||
|
||||
final ScrollController _scrollController;
|
||||
|
||||
final Map<int, MapItem> items = Map.fromEntries(
|
||||
List.generate(
|
||||
100,
|
||||
(i) => MapItem(name: 'Street $i', position: i, selected: false),
|
||||
).map(
|
||||
(item) => MapEntry(item.position, item),
|
||||
),
|
||||
);
|
||||
|
||||
MapController(this._scrollController);
|
||||
|
||||
void selectPosition(int position, {required bool selected}) {
|
||||
assert(
|
||||
position >= 0 && position < totalPositions,
|
||||
'Your position is out of our simulated map',
|
||||
);
|
||||
items[position] = items[position]!.copyWith(selected: selected);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
for (final position in items.keys) {
|
||||
items[position] = items[position]!.copyWith(selected: false);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void focus(int position) {
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) =>
|
||||
_scrollController.animateTo((position - 2) * itemHeight,
|
||||
duration: const Duration(milliseconds: 200), curve: Curves.ease));
|
||||
}
|
||||
}
|
||||
|
||||
class MapItem {
|
||||
final String name;
|
||||
final int position;
|
||||
final bool selected;
|
||||
|
||||
const MapItem({
|
||||
required this.name,
|
||||
required this.position,
|
||||
required this.selected,
|
||||
});
|
||||
|
||||
MapItem copyWith({
|
||||
bool? selected,
|
||||
}) {
|
||||
return MapItem(
|
||||
selected: selected ?? this.selected,
|
||||
name: name,
|
||||
position: position,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'map.dart';
|
||||
|
||||
class MapWidget extends StatefulWidget {
|
||||
final void Function(MapController map) onMapCreated;
|
||||
|
||||
const MapWidget({
|
||||
super.key,
|
||||
required this.onMapCreated,
|
||||
});
|
||||
|
||||
@override
|
||||
State<MapWidget> createState() => _MapWidgetState();
|
||||
}
|
||||
|
||||
class _MapWidgetState extends State<MapWidget> {
|
||||
late final Future<MapController> _future;
|
||||
late final ScrollController _scrollController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_scrollController = ScrollController();
|
||||
|
||||
// Simulate [MapWidget] loading process
|
||||
_future = Future<MapController>.delayed(
|
||||
const Duration(seconds: 1), () => MapController(_scrollController))
|
||||
.then((controller) {
|
||||
if (mounted) {
|
||||
widget.onMapCreated(controller);
|
||||
}
|
||||
return controller;
|
||||
});
|
||||
|
||||
_future.then((controller) => controller.addListener(_onControllerUpdate));
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_future
|
||||
.then((controller) => controller.removeListener(_onControllerUpdate));
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onControllerUpdate() => setState(() {});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => FutureBuilder(
|
||||
future: _future,
|
||||
builder: (context, snap) {
|
||||
if (!snap.hasData) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
final controller = snap.requireData;
|
||||
final list = controller.items.values.toList(growable: false);
|
||||
return ListView.builder(
|
||||
controller: _scrollController,
|
||||
itemCount: controller.items.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = list[index];
|
||||
return Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
listTileTheme: Theme.of(context).listTileTheme.copyWith(
|
||||
selectedTileColor:
|
||||
Theme.of(context).secondaryHeaderColor),
|
||||
),
|
||||
child: SizedBox(
|
||||
height: MapController.itemHeight,
|
||||
child: ListTile(
|
||||
selected: item.selected,
|
||||
title: Text(item.name),
|
||||
onTap: () {
|
||||
controller.selectPosition(
|
||||
item.position,
|
||||
selected: !item.selected,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'models/incoming_order_data.dart';
|
||||
|
||||
class IncomingOrdersProvider {
|
||||
IncomingOrderSession get incomingOrderSession =>
|
||||
IncomingOrderSession().._init();
|
||||
}
|
||||
|
||||
class IncomingOrderSession {
|
||||
final _controller = StreamController<IncomingOrder>.broadcast();
|
||||
late final StreamSubscription _subscription;
|
||||
|
||||
Stream<IncomingOrder> get incomingOrdersStream => _controller.stream;
|
||||
|
||||
bool get isPaused => _subscription.isPaused;
|
||||
|
||||
void _init() {
|
||||
_subscription = Stream<IncomingOrder>.periodic(
|
||||
const Duration(seconds: 5),
|
||||
(_) => IncomingOrder(
|
||||
fromAddress: _getRandomAddress(),
|
||||
toAddress: _getRandomAddress(),
|
||||
),
|
||||
).listen((order) {
|
||||
_log('income order ${order.uid}');
|
||||
_controller.add(order);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> dispose() async => await _subscription.cancel();
|
||||
|
||||
void pause() {
|
||||
_log('pause to receive orders');
|
||||
_subscription.pause();
|
||||
}
|
||||
|
||||
void resume() {
|
||||
_log('resume to receive orders');
|
||||
_subscription.resume();
|
||||
}
|
||||
|
||||
void _log(String message) {
|
||||
if (kDebugMode) {
|
||||
print(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Address _getRandomAddress() {
|
||||
final position = Random().nextInt(100);
|
||||
final street = 'Street $position';
|
||||
return Address(name: street, position: position);
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
import 'dart:math';
|
||||
|
||||
class IncomingOrder {
|
||||
static final _random = Random();
|
||||
|
||||
final DateTime createdTime;
|
||||
final String uid;
|
||||
final Address fromAddress;
|
||||
final Address toAddress;
|
||||
|
||||
IncomingOrder({
|
||||
required this.fromAddress,
|
||||
required this.toAddress,
|
||||
}) : createdTime = DateTime.now(),
|
||||
uid = _random.nextInt(100000).toString();
|
||||
}
|
||||
|
||||
class Address {
|
||||
final String name;
|
||||
final int position;
|
||||
|
||||
const Address({
|
||||
required this.name,
|
||||
required this.position,
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
import 'package:yx_scope/yx_scope.dart';
|
||||
|
||||
import '../../domain/auth/account_holder.dart';
|
||||
import '../../domain/auth/models/account.dart';
|
||||
import '../../domain/map/map_factory.dart';
|
||||
import '../../domain/order/models/order.dart';
|
||||
import '../../domain/order/orders_state_holder.dart';
|
||||
import '../../domain/order_navigation/order_navigation_delegate.dart';
|
||||
import '../app/app_scope.dart';
|
||||
import '../map/map_scope.dart';
|
||||
import '../online_order/online_scope.dart';
|
||||
import '../order/order_scope.dart';
|
||||
import '../utils/listeners.dart';
|
||||
|
||||
abstract class AccountScope implements Scope {
|
||||
Account get account;
|
||||
|
||||
OnlineScopeHolder get onlineScopeHolder;
|
||||
|
||||
OrderScopesHolder get orderScopesHolder;
|
||||
|
||||
MapScopeHolder get mapScopeHolder;
|
||||
|
||||
MapInitializer get mapInitializer;
|
||||
}
|
||||
|
||||
class AccountScopeContainer
|
||||
extends ChildDataScopeContainer<AppScopeContainer, Account>
|
||||
implements AccountScope, OnlineScopeParent, OrderScopeParent {
|
||||
AccountScopeContainer({
|
||||
required super.parent,
|
||||
required super.data,
|
||||
});
|
||||
|
||||
late final _onlineScopeHolderDep = dep(() => OnlineScopeHolder(this));
|
||||
|
||||
late final _orderScopesHolderDep = dep(() => OrderScopesHolder(this));
|
||||
|
||||
late final _mapScopeHolderDep = dep(() => MapScopeHolder());
|
||||
|
||||
late final _orderNavigationDelegateDep = dep(
|
||||
() => OrderNavigationDelegate(
|
||||
parent.routerDelegateDep.get,
|
||||
_mapScopeHolderDep.get,
|
||||
_orderScopesHolderDep.get,
|
||||
),
|
||||
);
|
||||
|
||||
@override
|
||||
Account get account => data;
|
||||
|
||||
@override
|
||||
OnlineScopeHolder get onlineScopeHolder => _onlineScopeHolderDep.get;
|
||||
|
||||
@override
|
||||
OrderScopesHolder get orderScopesHolder => _orderScopesHolderDep.get;
|
||||
|
||||
@override
|
||||
MapScopeHolder get mapScopeHolder => _mapScopeHolderDep.get;
|
||||
|
||||
@override
|
||||
OrdersHandler get ordersHandler => ordersStateHolder;
|
||||
|
||||
@override
|
||||
MapInitializer get mapInitializer => mapScopeHolder;
|
||||
|
||||
@override
|
||||
OrdersStateHolder get ordersStateHolder => orderScopesHolder;
|
||||
|
||||
@override
|
||||
void onStartOrderNavigation(Order order, int lastPosition) {
|
||||
_orderNavigationDelegateDep.get.onOrderNavigation(order, lastPosition);
|
||||
}
|
||||
}
|
||||
|
||||
class AccountScopeHolder extends BaseChildDataScopeHolder<
|
||||
AccountScope,
|
||||
AccountScopeContainer,
|
||||
AppScopeContainer,
|
||||
Account> implements AccountHolder {
|
||||
AccountScopeHolder(super.parent)
|
||||
: super(
|
||||
scopeObservers: [diObserver],
|
||||
depObservers: [diObserver],
|
||||
asyncDepObservers: [diObserver],
|
||||
);
|
||||
|
||||
@override
|
||||
AccountScopeContainer createContainer(
|
||||
AppScopeContainer parent, Account data) =>
|
||||
AccountScopeContainer(parent: parent, data: data);
|
||||
|
||||
@override
|
||||
Account? get account => scope?.account;
|
||||
|
||||
@override
|
||||
Stream<Account?> get accountStream => stream.map((scope) => scope?.account);
|
||||
|
||||
@override
|
||||
Future<void> setAccount(Account account) async {
|
||||
await _dropIfNeeded();
|
||||
await create(account);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dropAccount() async => _dropIfNeeded();
|
||||
|
||||
Future<void> _dropIfNeeded() async {
|
||||
if (account != null) {
|
||||
await drop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
import 'package:yx_scope/yx_scope.dart';
|
||||
|
||||
import '../../domain/auth/auth_manager.dart';
|
||||
import '../../router/app_state_observer.dart';
|
||||
import '../../router/router_delegate.dart';
|
||||
import '../account/account_scope.dart';
|
||||
import '../register/register_scope.dart';
|
||||
import '../utils/listeners.dart';
|
||||
|
||||
class AppScopeContainer extends ScopeContainer {
|
||||
@override
|
||||
List<Set<AsyncDep>> get initializeQueue => [
|
||||
{
|
||||
appStateObserverDep,
|
||||
}
|
||||
];
|
||||
|
||||
late final accountScopeHolderDep = dep(() => AccountScopeHolder(this));
|
||||
|
||||
late final registerScopeHolderDep = dep(() => RegisterScopeHolder(this));
|
||||
|
||||
late final authManagerDep = dep(
|
||||
() => AuthManager(
|
||||
accountScopeHolderDep.get,
|
||||
registerScopeHolderDep.get,
|
||||
),
|
||||
);
|
||||
|
||||
late final routerDelegateDep = dep(() => AppRouterDelegate());
|
||||
|
||||
late final appStateObserverDep = asyncDep(
|
||||
() => AppStateObserver(
|
||||
routerDelegateDep.get,
|
||||
accountScopeHolderDep.get,
|
||||
registerScopeHolderDep.get,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class AppScopeHolder extends ScopeHolder<AppScopeContainer> {
|
||||
AppScopeHolder()
|
||||
: super(
|
||||
scopeObservers: [diObserver],
|
||||
depObservers: [diObserver],
|
||||
asyncDepObservers: [diObserver],
|
||||
);
|
||||
|
||||
@override
|
||||
AppScopeContainer createContainer() => AppScopeContainer();
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
import 'package:yx_scope/yx_scope.dart';
|
||||
|
||||
import '../../data/map/map.dart';
|
||||
import '../../domain/map/map_factory.dart';
|
||||
import '../../domain/map/map_manager.dart';
|
||||
import '../../domain/map_navigation/map_navigation_manager.dart';
|
||||
import '../map_navigation/map_navigation_scope.dart';
|
||||
import '../utils/listeners.dart';
|
||||
|
||||
abstract class MapScope implements Scope {
|
||||
MapNavigationScopeHolder get mapNavigationScopeHolder;
|
||||
|
||||
MapController get controller;
|
||||
|
||||
MapManager get mapManager;
|
||||
|
||||
MapNavigationHolder get mapNavigationHolder;
|
||||
}
|
||||
|
||||
class MapScopeContainer extends DataScopeContainer<MapController>
|
||||
implements MapScope, MapNavigationParent {
|
||||
MapScopeContainer({required super.data});
|
||||
|
||||
late final _mapNavigationScopeHolderDep =
|
||||
dep(() => MapNavigationScopeHolder(this));
|
||||
|
||||
late final _mapManagerDep =
|
||||
dep(() => MapManager(data, _mapNavigationScopeHolderDep.get));
|
||||
|
||||
@override
|
||||
MapNavigationScopeHolder get mapNavigationScopeHolder =>
|
||||
_mapNavigationScopeHolderDep.get;
|
||||
|
||||
@override
|
||||
MapManager get mapManager => _mapManagerDep.get;
|
||||
|
||||
@override
|
||||
MapController get controller => data;
|
||||
|
||||
@override
|
||||
MapNavigationHolder get mapNavigationHolder =>
|
||||
_mapNavigationScopeHolderDep.get;
|
||||
|
||||
@override
|
||||
void onNavigationStop() => _mapNavigationScopeHolderDep.get.drop();
|
||||
}
|
||||
|
||||
class MapScopeHolder
|
||||
extends BaseDataScopeHolder<MapScope, MapScopeContainer, MapController>
|
||||
implements MapInitializer {
|
||||
MapScopeHolder()
|
||||
: super(
|
||||
scopeObservers: [diObserver],
|
||||
depObservers: [diObserver],
|
||||
asyncDepObservers: [diObserver],
|
||||
);
|
||||
|
||||
@override
|
||||
MapScopeContainer createContainer(MapController data) =>
|
||||
MapScopeContainer(data: data);
|
||||
|
||||
@override
|
||||
Future<void> createMap(MapController mapController) => create(mapController);
|
||||
|
||||
@override
|
||||
Future<void> dropMap() => drop();
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
import 'package:yx_scope/yx_scope.dart';
|
||||
|
||||
import '../../domain/map/map_manager.dart';
|
||||
import '../../domain/map_navigation/map_navigation_manager.dart';
|
||||
import '../utils/listeners.dart';
|
||||
|
||||
abstract class MapNavigationScope {
|
||||
MapNavigationManager get mapNavigationManager;
|
||||
}
|
||||
|
||||
abstract class MapNavigationParent implements Scope {
|
||||
MapManager get mapManager;
|
||||
|
||||
void onNavigationStop();
|
||||
}
|
||||
|
||||
class MapNavigationScopeContainer
|
||||
extends ChildDataScopeContainer<MapNavigationParent, MapNavigationParams>
|
||||
implements MapNavigationScope {
|
||||
MapNavigationScopeContainer({required super.parent, required super.data});
|
||||
|
||||
@override
|
||||
List<Set<AsyncDep>> get initializeQueue => [
|
||||
{_mapNavigationManagerDep}
|
||||
];
|
||||
|
||||
late final _mapNavigationManagerDep = rawAsyncDep<MapNavigationManager>(
|
||||
() => MapNavigationManager(
|
||||
data,
|
||||
parent.mapManager,
|
||||
parent.onNavigationStop,
|
||||
),
|
||||
init: (dep) async => dep.prepare(),
|
||||
dispose: (dep) async {},
|
||||
);
|
||||
|
||||
@override
|
||||
MapNavigationManager get mapNavigationManager => _mapNavigationManagerDep.get;
|
||||
}
|
||||
|
||||
class MapNavigationScopeHolder extends BaseChildDataScopeHolder<
|
||||
MapNavigationScope,
|
||||
MapNavigationScopeContainer,
|
||||
MapNavigationParent,
|
||||
MapNavigationParams> implements MapNavigationHolder {
|
||||
MapNavigationScopeHolder(super.parent)
|
||||
: super(
|
||||
scopeObservers: [diObserver],
|
||||
depObservers: [diObserver],
|
||||
asyncDepObservers: [diObserver],
|
||||
);
|
||||
|
||||
@override
|
||||
MapNavigationScopeContainer createContainer(
|
||||
MapNavigationParent parent,
|
||||
MapNavigationParams data,
|
||||
) =>
|
||||
MapNavigationScopeContainer(parent: parent, data: data);
|
||||
|
||||
@override
|
||||
Future<void> startNavigation(MapNavigationParams navigationParams) =>
|
||||
create(navigationParams);
|
||||
|
||||
@override
|
||||
Future<void> stopNavigation() => drop();
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:yx_scope/yx_scope.dart';
|
||||
|
||||
import '../../data/orders/incoming_orders_provider.dart';
|
||||
import '../../domain/order/accept_order_manager.dart';
|
||||
import '../../domain/order/online_order_state_holder.dart';
|
||||
import '../../domain/order/orders_state_holder.dart';
|
||||
import '../utils/listeners.dart';
|
||||
|
||||
abstract class OnlineScope implements Scope {
|
||||
AcceptOrderManager get acceptOrderManager;
|
||||
}
|
||||
|
||||
abstract class OnlineScopeParent implements Scope {
|
||||
OrdersStateHolder get ordersStateHolder;
|
||||
}
|
||||
|
||||
class OnlineScopeContainer extends ChildScopeContainer<OnlineScopeParent>
|
||||
implements OnlineScope {
|
||||
OnlineScopeContainer({required super.parent});
|
||||
|
||||
@override
|
||||
List<Set<AsyncDep>> get initializeQueue => [
|
||||
{_acceptOrderManagerDep}
|
||||
];
|
||||
|
||||
late final _incomingOrdersProviderDep = dep(() => IncomingOrdersProvider());
|
||||
|
||||
late final _acceptOrderManagerDep = asyncDep(() => AcceptOrderManager(
|
||||
parent.ordersStateHolder,
|
||||
_incomingOrdersProviderDep.get,
|
||||
));
|
||||
|
||||
@override
|
||||
AcceptOrderManager get acceptOrderManager => _acceptOrderManagerDep.get;
|
||||
}
|
||||
|
||||
class OnlineScopeHolder extends BaseChildScopeHolder<OnlineScope,
|
||||
OnlineScopeContainer, OnlineScopeParent> implements OnlineOrderStateHolder {
|
||||
OnlineScopeHolder(super.parent)
|
||||
: super(
|
||||
scopeObservers: [diObserver],
|
||||
depObservers: [diObserver],
|
||||
asyncDepObservers: [diObserver],
|
||||
);
|
||||
|
||||
@override
|
||||
OnlineScopeContainer createContainer(OnlineScopeParent parent) =>
|
||||
OnlineScopeContainer(parent: parent);
|
||||
|
||||
@override
|
||||
Stream<bool> get isOnlineStream => stream.map((scope) => scope != null);
|
||||
|
||||
@override
|
||||
bool get isOnline => scope != null;
|
||||
|
||||
@override
|
||||
Future<void> toggle() async {
|
||||
if (scope == null) {
|
||||
await create();
|
||||
return;
|
||||
}
|
||||
await drop();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:yx_scope/yx_scope.dart';
|
||||
|
||||
import '../../domain/order/models/order.dart';
|
||||
import '../../domain/order/order_manager.dart';
|
||||
import '../../domain/order/order_position_manager.dart';
|
||||
import '../../domain/order/orders_state_holder.dart';
|
||||
import '../utils/listeners.dart';
|
||||
|
||||
abstract class OrderScope implements Scope {
|
||||
OrderManager get orderManager;
|
||||
|
||||
OrderPositionHolder get orderPositionHolder;
|
||||
}
|
||||
|
||||
abstract class OrderScopeParent implements Scope {
|
||||
OrdersHandler get ordersHandler;
|
||||
|
||||
void onStartOrderNavigation(Order order, int lastPosition);
|
||||
}
|
||||
|
||||
class OrderScopeContainer
|
||||
extends ChildDataScopeContainer<OrderScopeParent, Order>
|
||||
implements OrderScope {
|
||||
OrderScopeContainer({
|
||||
required super.parent,
|
||||
required super.data,
|
||||
});
|
||||
|
||||
late final _orderManagerDep = dep(
|
||||
() => OrderManager(
|
||||
data,
|
||||
parent.ordersHandler,
|
||||
_orderPositionHolderDep.get,
|
||||
parent.onStartOrderNavigation,
|
||||
),
|
||||
);
|
||||
|
||||
late final _orderPositionHolderDep = dep(() => OrderPositionHolder(data));
|
||||
|
||||
@override
|
||||
OrderManager get orderManager => _orderManagerDep.get;
|
||||
|
||||
@override
|
||||
OrderPositionHolder get orderPositionHolder => _orderPositionHolderDep.get;
|
||||
}
|
||||
|
||||
class OrderScopesHolder implements OrdersStateHolder {
|
||||
final OrderScopeParent parent;
|
||||
|
||||
final _controller =
|
||||
StreamController<Map<Order, OrderScopeHolder>>.broadcast();
|
||||
|
||||
final orderScopes = <Order, OrderScopeHolder>{};
|
||||
|
||||
Stream<Map<Order, OrderScopeHolder>> get orderScopesStream =>
|
||||
_controller.stream;
|
||||
|
||||
OrderScopesHolder(this.parent);
|
||||
|
||||
@override
|
||||
Future<void> addOrder(Order order) async {
|
||||
final holder = OrderScopeHolder(parent);
|
||||
orderScopes[order] = holder;
|
||||
await holder.create(order);
|
||||
_notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> removeOrder(Order order) async {
|
||||
await orderScopes.remove(order)?.drop();
|
||||
_notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
List<OrderManager> get orders =>
|
||||
orderScopes.values.map((e) => e.scope).toList().fold(
|
||||
[],
|
||||
(previousValue, scope) {
|
||||
if (scope != null) {
|
||||
previousValue.add(scope.orderManager);
|
||||
}
|
||||
return previousValue;
|
||||
},
|
||||
);
|
||||
|
||||
void _notifyListeners() {
|
||||
_controller.add(orderScopes);
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<List<OrderManager>> get ordersStream =>
|
||||
_controller.stream.map((scopes) => scopes.values
|
||||
.map((holder) => holder.scope)
|
||||
.whereType<OrderScopeContainer>()
|
||||
.map((scope) => scope._orderManagerDep.get)
|
||||
.toList(growable: false));
|
||||
}
|
||||
|
||||
class OrderScopeHolder extends BaseChildDataScopeHolder<OrderScope,
|
||||
OrderScopeContainer, OrderScopeParent, Order> {
|
||||
OrderScopeHolder(super.parent)
|
||||
: super(
|
||||
scopeObservers: [diObserver],
|
||||
depObservers: [diObserver],
|
||||
asyncDepObservers: [diObserver],
|
||||
);
|
||||
|
||||
@override
|
||||
OrderScopeContainer createContainer(OrderScopeParent parent, Order data) =>
|
||||
OrderScopeContainer(parent: parent, data: data);
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
import 'package:yx_scope/yx_scope.dart';
|
||||
|
||||
import '../../domain/auth/register_manager.dart';
|
||||
import '../../domain/auth/register_state_holder.dart';
|
||||
import '../app/app_scope.dart';
|
||||
import '../utils/listeners.dart';
|
||||
|
||||
class RegisterScopeContainer extends ChildScopeContainer<AppScopeContainer> {
|
||||
RegisterScopeContainer({required super.parent});
|
||||
|
||||
late final registerManagerDep = dep(
|
||||
() => RegisterManager(
|
||||
parent.accountScopeHolderDep.get,
|
||||
parent.registerScopeHolderDep.get,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class RegisterScopeHolder
|
||||
extends ChildScopeHolder<RegisterScopeContainer, AppScopeContainer>
|
||||
implements RegisterHolder {
|
||||
RegisterScopeHolder(super.parent)
|
||||
: super(
|
||||
scopeObservers: [diObserver],
|
||||
depObservers: [diObserver],
|
||||
asyncDepObservers: [diObserver],
|
||||
);
|
||||
|
||||
@override
|
||||
RegisterScopeContainer createContainer(parent) =>
|
||||
RegisterScopeContainer(parent: parent);
|
||||
|
||||
@override
|
||||
bool get inProgress => scope != null;
|
||||
|
||||
@override
|
||||
Stream<bool> get inProgressStream => stream.map((scope) => scope != null);
|
||||
|
||||
@override
|
||||
Future<void> startRegister() async => await create();
|
||||
|
||||
@override
|
||||
Future<void> stopRegister() async => await drop();
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:yx_scope/yx_scope.dart';
|
||||
|
||||
const diObserver = DIObserver();
|
||||
|
||||
class DIObserver implements ScopeObserver, DepObserver, AsyncDepObserver {
|
||||
const DIObserver();
|
||||
|
||||
static void _log(
|
||||
String message, [
|
||||
Object? exception,
|
||||
StackTrace? stackTrace,
|
||||
]) {
|
||||
if (kDebugMode) {
|
||||
print(message);
|
||||
if (exception != null) {
|
||||
print(exception);
|
||||
if (stackTrace != null) {
|
||||
print(stackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onScopeStartInitialize(ScopeId scope) =>
|
||||
_log('[$scope] -> onScopeStartInitialize');
|
||||
|
||||
@override
|
||||
void onScopeInitialized(ScopeId scope) =>
|
||||
_log('[$scope] -> onScopeInitialized');
|
||||
|
||||
@override
|
||||
void onScopeInitializeFailed(
|
||||
ScopeId scope,
|
||||
Object exception,
|
||||
StackTrace stackTrace,
|
||||
) =>
|
||||
_log('[$scope] -> onScopeInitializeFailed', exception, stackTrace);
|
||||
|
||||
@override
|
||||
void onScopeStartDispose(ScopeId scope) =>
|
||||
_log('[$scope] -> onScopeStartDispose');
|
||||
|
||||
@override
|
||||
void onScopeDisposed(ScopeId scope) => _log('[$scope] -> onScopeDisposed');
|
||||
|
||||
@override
|
||||
void onScopeDisposeDepFailed(
|
||||
ScopeId scope,
|
||||
DepId dep,
|
||||
Object exception,
|
||||
StackTrace stackTrace,
|
||||
) =>
|
||||
_log('[$scope] -> onScopeDisposeDepFailed', exception, stackTrace);
|
||||
|
||||
@override
|
||||
void onValueStartCreate(ScopeId scope, DepId dep) =>
|
||||
_log('[$scope.$dep] -> onValueStartCreate');
|
||||
|
||||
@override
|
||||
void onValueCreated(ScopeId scope, DepId dep, ValueMeta? valueMeta) =>
|
||||
_log('[$scope.$dep] -> onValueCreated');
|
||||
|
||||
@override
|
||||
void onValueCreateFailed(
|
||||
ScopeId scope,
|
||||
DepId dep,
|
||||
Object exception,
|
||||
StackTrace stackTrace,
|
||||
) =>
|
||||
_log('[$scope.$dep] -> onValueCreated', exception, stackTrace);
|
||||
|
||||
@override
|
||||
void onValueCleared(ScopeId scope, DepId dep, ValueMeta? valueMeta) =>
|
||||
_log('[$scope.$dep]($valueMeta) -> onValueCleared');
|
||||
|
||||
@override
|
||||
void onDepStartInitialize(ScopeId scope, DepId dep) =>
|
||||
_log('[$scope.$dep] -> onDepStartInitialize');
|
||||
|
||||
@override
|
||||
void onDepInitialized(ScopeId scope, DepId dep) =>
|
||||
_log('[$scope.$dep] -> onDepInitialized');
|
||||
|
||||
@override
|
||||
void onDepStartDispose(ScopeId scope, DepId dep) =>
|
||||
_log('[$scope.$dep] -> onDepStartDispose');
|
||||
|
||||
@override
|
||||
void onDepDisposed(ScopeId scope, DepId dep) =>
|
||||
_log('[$scope.$dep] -> onDepDisposed');
|
||||
|
||||
@override
|
||||
void onDepInitializeFailed(
|
||||
ScopeId scope,
|
||||
DepId dep,
|
||||
Object exception,
|
||||
StackTrace stackTrace,
|
||||
) =>
|
||||
_log('[$scope.$dep] -> onDepInitializeFailed', exception, stackTrace);
|
||||
|
||||
@override
|
||||
void onDepDisposeFailed(
|
||||
ScopeId scope,
|
||||
DepId dep,
|
||||
Object exception,
|
||||
StackTrace stackTrace,
|
||||
) =>
|
||||
_log('[$scope.$dep] -> onDepDisposeFailed', exception, stackTrace);
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import 'models/account.dart';
|
||||
|
||||
abstract class AccountHolder {
|
||||
Account? get account;
|
||||
|
||||
Stream<Account?> get accountStream;
|
||||
|
||||
Future<void> setAccount(Account account);
|
||||
|
||||
Future<void> dropAccount();
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
import 'account_holder.dart';
|
||||
import 'models/account.dart';
|
||||
import 'models/account_params.dart';
|
||||
import 'register_state_holder.dart';
|
||||
|
||||
class AuthManager {
|
||||
final AccountHolder _accountHolder;
|
||||
final RegisterHolder registerStateHolder;
|
||||
|
||||
AuthManager(this._accountHolder, this.registerStateHolder);
|
||||
|
||||
Future<void> login(AccountParams loginParams) async {
|
||||
await _accountHolder.setAccount(Account('123', 'Bob'));
|
||||
}
|
||||
|
||||
Future<void> logout() async {
|
||||
await _accountHolder.dropAccount();
|
||||
}
|
||||
|
||||
Future<void> startRegister() async {
|
||||
await registerStateHolder.startRegister();
|
||||
}
|
||||
|
||||
Future<void> stopRegister() async {
|
||||
await registerStateHolder.stopRegister();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
class Account {
|
||||
final String uid;
|
||||
final String name;
|
||||
|
||||
Account(this.uid, this.name);
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
class AccountParams {
|
||||
final String login;
|
||||
final String password;
|
||||
|
||||
AccountParams(this.login, this.password);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue