Fix backend: Register Isar in GetIt, remove broken lib download

This commit is contained in:
Dmitry 2025-11-19 22:03:20 +03:00
parent a26fcc4cfa
commit 736997733a
257 changed files with 16236 additions and 0 deletions

9
.access Normal file
View 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
View file

View 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);

View 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

View 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"
}

View file

@ -0,0 +1,4 @@
{
"project":"city-services-pub",
"repository":"arcadia"
}

View file

@ -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.

View 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.

View 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

View file

@ -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

View file

@ -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.

View file

@ -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.

View file

@ -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

View 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

View file

@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: cfa03f886884e42676f1834864657721a1219624
channel: stable
project_type: package

View file

@ -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

View file

@ -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.

View file

@ -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.**
[![Pub Version](https://img.shields.io/pub/v/yx_scope)](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.
![Scope Anatomy](https://github.com/yandex/city-services-pub/blob/main/yx_scope/packages/yx_scope/doc/assets/scope_anatomy.png?raw=true)
## 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).

View file

@ -0,0 +1 @@
include: package:lints/recommended.yaml

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View file

@ -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"
}
}
```

View 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

View file

@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "4cf269e36de2573851eaef3c763994f8f9be494d"
channel: "stable"
project_type: package

View file

@ -0,0 +1,3 @@
## 0.0.1
* TODO: Describe initial release.

View file

@ -0,0 +1 @@
TODO: Add your license here.

View file

@ -0,0 +1 @@
## An example for yx_scope

View file

@ -0,0 +1 @@
include: package:lints/recommended.yaml

View file

@ -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');
}

View file

@ -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);
}

View file

@ -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');
}

View file

@ -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

View file

@ -0,0 +1,2 @@
export 'src/monitoring/raw_observers.dart';
export 'src/base_scope_container.dart' show CustomDep, CustomAsyncDep;

View file

@ -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._();
}

View file

@ -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._();
}

View file

@ -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';
}

View file

@ -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';
}

View file

@ -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';
}
}

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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 {}

View file

@ -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]';
}

View file

@ -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]';
}

View file

@ -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;
}

View file

@ -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]';
}

View file

@ -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._();
}

View file

@ -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);
}
}

View file

@ -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;
}

View file

@ -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,
);
}

View file

@ -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,
}

View file

@ -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);
}

View file

@ -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(', ')}]',
);
}

View file

@ -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> {}

View file

@ -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,
);
}

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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';

View file

@ -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

View file

@ -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;
}

View file

@ -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>(),
),
);
});
}

View file

@ -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 {}

View file

@ -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 {}

View file

@ -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();
}

View file

@ -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 {},
);
}

View file

@ -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 {}

View file

@ -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');
}
}
}

View file

@ -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);

View 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

View file

@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: e2874b6e235bcae425ed3436c113ab4ac50ac707
channel: unknown
project_type: package

View file

@ -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

View file

@ -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.

View file

@ -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).

View file

@ -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

View file

@ -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 Anatomy](assets/scope_anatomy.png)
## 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.
![Scope Tree](assets/scope_tree.png)
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
![Scope Types](assets/scope_types.png)
## 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.
Lets 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.
Lets 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, youll 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 well
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`—dont let that confuse you.

View file

@ -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

View file

@ -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'

View file

@ -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.

View file

@ -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

View file

@ -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,
);
}
}

View file

@ -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,
);
},
),
),
);
},
);
},
);
}

View file

@ -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);
}

View file

@ -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,
});
}

View file

@ -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();
}
}
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
}
}

View file

@ -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);
}

View file

@ -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();
}

View file

@ -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);
}

View file

@ -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();
}

View file

@ -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();
}
}

View file

@ -0,0 +1,6 @@
class Account {
final String uid;
final String name;
Account(this.uid, this.name);
}

View file

@ -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